在本章中,你将学习:
认识链接列表的特性
执行单链接列表
假定您已经编写了一个算法来 产生并存储 1 到 10,00,000 之间的所有质数,然 后显示它们。
您如何解决这个问题?
考虑以下使用数组来解决此问题的算法:
1. Set I = 0
2. Repeat step 3 varying N from 2 to 1000000
3. If N is a prime number
a. Set A[I] = N
b. I = I + 1
4. Repeat step 5 varying J from 0 to I-1
5. Display A[J]
此算法有什么问题?
1 到 10,00,000 之间的质数个数还不知道。如果使用数组来存储这些质数,您不 得不武断地声明一个相当大的数组来存储这些质数。
此方法的缺点,假定您声明数组大小为 N :
如果 1 到 10,00,000 之间的质数个数多于 N ,就不能存储所有的质数。
如果质数个数比 N 小得多,则就浪费了许多空间。
因此,如果您预先不知道元素的总数您就不能使用数组来存储这些数。
那你该怎么做?
可以使用动态数据结构,它不需要预先指定大小,当需要内存时可以随时进行分 配。
在你声明一个数组时分配连续的内存块。
让我们假定声明一个大小为 10 的数组以存储前 10 个质数。
如果您知道数组的第一个元素的地址,就可以容易地计算出其他元素的地址。 如下所示:
第一个元素的地址 + ( 元素的大小 × 元素索引 )
动态分配内存时,可以从内存的任何位置分配内存块。
因此,与数组不同,这些块可能会不连续,它们可能会随机分布在内存中。
要访问任何元素,需要知道其地址。
现在,如果您知道第一个元素的地址,也无法计算出其他元素的地址。
这是因为所有的元素都是随机存储在内存中。
因此,使每个分配的内存块能够按序保持下一个块的地址是十分必要的。
这样为内存块给定了一个链接结构,每个块都链接到序列中的下一块。
实现此概念的数据结构的示例就是链接列表。
你可以声明一个变量 START ,它存储第一个块的地址。
你现在可以从 START 开始并通过下面的链接穿过列表。
链接列表:
是一个动态数据结构。
在需要时允许你分配内存。
由元素链组成,在此元素链中每一个元素都被指定为一个节点。
节点是链接列表的基本构建块。
一个节点由两部分组成:
数据: 指的是节点保存的信息。
链接: 保存列表中下一个节点的地址。
链接列表中的最后一个节点不指向任何其它节点。因此,它指向 NULL 。
链接列表中的所有节点是显示在内存空间的任意位置的。
因此,链接列表中的每一个节点都有存储序列中下一个节点地址的链接字段。
要了解列表的第一个节点,声明一个变量 / 指示字 START ,它总是指向第一 个节点。
当列表为空时, START 包含 NULL 。
让我们使用链接列表解决给定的存储质数的问题。
1. 重复步骤 2 , N 介于 2 到 1000000 。
2. 如果 N 是一个质数,将它插入到链接列表中。
a. 为新节点分配内存。
b. 在新节点中存储此质数。
c. 在链接列表中附加新节点。
3. 显示存储在链接列表中的质数。
考虑给出的算法以在链接表中插入节点。
•
1. 为新节点分配内存。
2.
2. 为新节点的数据字段分配值。
3.
3. 如果 START 是 NULL ,则:
a. 使 START 指向新的节点。
b. 转至步骤 6 。
4.
4. 找到列表中最后一个节点,将它标记为 currentNode 。要找到列表中的最后一个节点,请执行以下步骤:
a. 使第一个节点成为 currentNode 。
b. 重复步骤 c ,直到 currentNode 的 successor 变成 NULL 。
c. 使 currentNode 指向序列中的下一个节点。
5. 使 currentNode 的 next 字段指向新节点。
6.
6. 使新节点的 next 字段指向 NULL 。
为遍历链接表运用算法。
1. 使 currentNode 指向列表中的第一个节点。
2.
2. 重复步骤 3 和步骤 4 ,直到 currentNode 成为 NULL 为止。
3.
3. 显示标记为 currentNode 的节点中包含的信息。
4.
4. 使 currentNode 指向序列中的下一个节点。
编写一算法以在链接列表中删除第一个节点。
1. 使列表中的第一个节点成为当前节点。
2.
2. 使 START 指向序列中的下一个节点。
3.
3. 释放标记为当前节点的内存。
编写一算法以删除链接列表中两个节点之间的节点。
1. 找到要删除的节点,将要删除的节点标记为当前节点,将它前一个节点标记为前一个节点。要找到当前节点和前一个节点,请 执行以下步骤:
a. 将前一个节点设置为 START 。
b. 将当前节点设置为 START 。
c. 重复步骤 d 和 e ,直到找到该节点或当前节点变成 NULL 为止。
d. 使前一个节点指向当前节点。
e. 使当前节点指向序列中的下一个节点。
2.
2. 使前一个节点的 next 字段指向当前节点的后继者。
3.
3. 释放标记为当前节点的节点内存。
通过定义两个类,在程序中表示链接列表:
Node 类: 节点是链接列表中的基础构建块。 Node 类代表链接列表中的节点。 它包括各种数据类型的数据成员,代表要在链接列表中存储的数据,它还包含对 类类型( node )的引用以保持对序列中下一个节点的引用。
// C# 代码
class Node
{
public int data;
public Node next; // 包含序列中下一个
// 节点地址的变量
}
// C++ 代码
class Node
{
public:
int data;
Node *next; // 指向序列中下一个节点
};
List 类: 该类由一组执行链接列表的操作组成。这些操作是插入、删除、搜索和 遍历。它还包含变量 / 指针 START 的声明, START 始终指向列表中的第一个节点。
// C# 代码
class Node
{
public int data;
public Node next; // 包含序列中下一个
// 节点地址的变量
}
// C# 代码
class List
{
private Node START;
List()
{
START = NULL;
}
public void addNode(int element){}
public bool search(int element, ref Node previous, ref Node current) {}
public bool delNode(int element) {}
public void traverse() {}
}
// C# 代码
class List
{
Node * START;
public:
List()
{
START = NULL;
}
void addNode(int element);
bool search(int element, Node *previous, Node *current);
bool delNode(int element);
void traverse();
};
问题描述:
编写一个程序,对单链接列表执行插入、搜索、删除和遍历操作,该列 表存储了班级中的学生记录。每个记录具有以下信息:
学生的注册号
学生姓名
小结
在本章中,你已经学到:
单链接列表中,每个节点包含:
信息
列表中下一个节点的地址
单链接列表只可以按一个方向遍历。
链接列表中的删除和插入操作比数组快,但是,链接列表中的元素访问速度比数 组要慢。
/*创建链表,通过使用节点类来创建*/using System;class Node{ public int data; //data用来存放节点的数据 public Node next; //next(节点对象)用来存放下一个节点的地址}class CreateList{ private Node start; //头指针存放第一个节点的地址. private Node current; //定义个当前节点对象,用来遍历链表. private Node previous; //前驱节点. public CreateList() { start=null; //当初始化类时,设置头指针为空 } /*优化的创建链表的方法*/ public void create2() { previous=current=start=null; //最开始的时候都为空,就是链表为一个空链表. Console.Write("请输入您要创建链表的个数:"); int k=Convert.ToInt32(Console.ReadLine()); for(int i=0;i=current.data)) { if(i==current.data) { Console.WriteLine("\n重复数据不允许"); return; } previous=current; current=current.next; } /*找到前驱节点和后继节点之后*/ newnode.next=current; previous.next=newnode; } //搜索链表元素 public void search() { Console.WriteLine("请输入您要查找的数据:"); int i=Convert.ToInt32(Console.ReadLine()); //1.定义一个要搜索的数字 Node current=start; //2.定义当前节点对象current while((current!=null)&&(current.data!=i)) //3.如果当前节点不为空,并且当前节点数据不等于你输入的数据,则循环. { current=current.next; } //4。判断是找到了还是没有找到 if(current==null) Console.WriteLine("没有找到"); else Console.WriteLine("已经找到了您输入的数据"); } //删除链表元素 public void delete() { Console.WriteLine("请输入您要删除的元素"); int i=Convert.ToInt32(Console.ReadLine()); previous=current=start; //设定当前节点为头指针指向的节点. if(start==null) { Console.WriteLine("吃饱了撑的,开始乱删了,滚!XXXXXXXX"); }else if(current.data==i) { start=current.next; current.next=null; //书上忽略了,由系统自动回收不用的内存. }else //否则,有多个节点,要删除我们指定的节点. { while((current!=null)&&(current.data!=i)) {//要找下一个符合条件的节点 previous=current; //在找符合要求的节点之前,将原来节点的地址给previous current=current.next; //当前节点指向下一个节点. } if(current==null) Console.WriteLine("没有找到!"); else previous.next=current.next; //将要删除的节点的后继节点地址给前驱节点的next. } } /* 1.定义要删除节点的前驱节点对象:previous;要删除节点:current 2.previous和current都指向start 3.遍历链表,以便找到要删除的节点.到底什么时候遍历链表呢? while(current!=null)并且(current.data!=17) { previous=current; current=current.next; // 要查找下一个节点,同时,前驱节点指向刚才的current节点. } 如果current==空:null 说明没有找到 else 找到了则: previous.next=current.next; */ public static void Main(string[]args) { CreateList list=new CreateList(); //list.create2(); //创建链表 list.scan(); list.insert(); list.delete(); list.scan(); //list.search(); }}
/*针对链表为空的时候进行插入一个节点的实现;链表的实现通过节点类来实现.*/using System;class Node //定义一个节点类{ public int data; //数据 public Node next; //next:存放下一个节点的地址. }//定义实现对节点操作的类class LinkedListInsert{ //定义一个头指针的属性 private Node Start; private Node current; //当前节点. //定义构造方法 public LinkedListInsert() { Start=null; } //定义一个静态插入方法 public void insert() { //直接赋值 if(Start==null) { //1.第一步是给新的节点分配内存 Node first= new Node(); //分3步画图,记到本上. //2.设置新节点的数据为2 first.data=2; //3.将新节点(即质数为2的节点)的地址给头指针,即头指针指向质数为2的节点 Start=first; //4.设置fist的下一个节点为空(null) first.next=null; //----------------------下面所实现的就应该再插入一个节点的情况;仍然按照插入第一个节点的情况步骤来做,不同点注意将新的点地址给谁了? Node second=new Node(); second.data=3; first.next =second; //将质数3的节点放到第一个节点地址域 second.next=null; //-----------------------质数为5的节点 Node third=new Node(); third.data=5; second.next=third; third.next=null; } } //浏览节点方法 public void scan() { //需要判断,如果节点不为空,则输出节点的数据域里的内容 current=Start; int i=0; while(current!=null) { Console.WriteLine("第{0}节点的数据为:{1}",i+1,current.data); current=current.next; i++; } } public static void Main(string[]args) { LinkedListInsert list=new LinkedListInsert(); list.insert(); list.scan(); }}
/*问题描述:对单链接列表执行插入、搜索、删除和遍礼操作,该列表存储了班级中的学生记录。每个记录具有如下信息:学生的注册号学生的姓名*/using System;using System.Text;namespace Sort{ /*每个节点由信息部分和到下一个节点的地址(链接)组成*/ class Node { public int rollNumber; public string name; //上面的学号和姓名为数据 public Node next; //为地址 } class List { Node Start; public List() { Start=null; } public void addNode() /*在列表中添加一个节点*/ { int rollNo; string sName; Console.Write("\n请输入学生的学号:"); rollNo=Convert.ToInt32(Console.ReadLine()); Console.Write("\n请输入学生的姓名:"); sName=Console.ReadLine(); Node newNode=new Node(); //声明新节点并赋值 newNode.rollNumber=rollNo; newNode.name=sName; /*如果要插入的节点是第一个节点*/ if((Start==null)||(rollNo<=Start.rollNumber)) { if((Start!=null)&&(rollNo==Start.rollNumber)) { Console.WriteLine("\n重复的数据不允许被插入"); return; }//不允许重复的学号. newNode.next=Start; Start=newNode; return; } /*在列表中找到新节点的位置*/ Node previous,current; //定义我们要插入节点的前驱节点和后继节点对象. previous=Start; current=Start; while((current!=null)&&(rollNo>=current.rollNumber)) { if(rollNo==current.rollNumber) { Console.WriteLine("\n重复数据不允许\n"); return; } previous=current; current=current.next; } /*一旦执行上述循环,新节点的位置就位于前一个和当前节点之间*/ newNode.next=current; //新节点指向后继节点 previous.next=newNode; //将前驱节点指向新节点. } /*------------------------------------------------------------------*/ public bool delNode(int rollNo) /*从列表中删除指定的节点*/ { Node previous,current; previous=current=null; if(Search(rollNo,ref previous,ref current)==false) /*检查指定的节点是否在列表中*/ return false; previous.next=current.next; if(current==Start) Start=Start.next; return true; } public bool Search(int rollNo,ref Node previous,ref Node current) /*检查指定的节点是否在列表中*/ { previous=Start; current=Start; while((current!=null)&&(rollNo!=current.rollNumber)) { previous=current; current=current.next; } if(current==null) return (false); else return (true); } /*遍历该列表*/ public void traverse() { if(listEmpty()) Console.WriteLine("\n列表是空的\n"); else { Console.WriteLine("列表的记录是:\n"); Node currentNode; for(currentNode=Start;currentNode!=null;currentNode=currentNode.next) Console.Write(currentNode.rollNumber+" "+currentNode.name+"\n"); Console.WriteLine(); } } /*---------------------判断列表是否为空-------------------*/ public bool listEmpty() { if(Start==null) return true; else return false; } /*-----------------------Main()方法--------------------*/ public static void Main(string[]args) { List obj=new List(); while(true) { try { Console.WriteLine("\n菜单"); Console.WriteLine("1.增加列表"); Console.WriteLine("2.删除列表"); Console.WriteLine("3.遍历列表"); Console.WriteLine("4.搜索列表"); Console.WriteLine("5.退出"); Console.Write("\n请输入您的选择(1-5):"); char ch=Convert.ToChar(Console.ReadLine()); switch(ch) { case '1': obj.addNode(); break; case '2': { if(obj.listEmpty()) { Console.WriteLine("\n列表为空"); break; } Console.Write("\n请输入您要删除学生记录的学号:"); int rollNo=Convert.ToInt32(Console.ReadLine()); Console.WriteLine(); if(obj.delNode(rollNo)==false) Console.WriteLine("\n记录没有发现."); else Console.WriteLine("记录为"+rollNo+"已经删除"); } break; case '3': obj.traverse(); break; case '4': { if(obj.listEmpty()==true) { Console.WriteLine("\n列表为空"); break; } Node previous,current; previous=current=null; Console.Write("\n请输入您要搜索学生的学号:"); int num=Convert.ToInt32(Console.ReadLine()); if(obj.Search(num,ref previous,ref current)==false) Console.WriteLine("\n记录没有发现."); else { Console.WriteLine("\n记录发现"); Console.WriteLine("\n学号为:"+current.rollNumber); Console.WriteLine("\n姓名:"+current.name); } } break; case '5': return; default: Console.WriteLine("\n无效选项"); break; } } catch(Exception e) { Console.WriteLine("\n请检查输入的值."); } } } }}