上一篇我们总结完了顺序表,这一篇我们要总结的是线性表的链表,我想从以下几点进行总结。
1,为什么要使用链表?
2,链表的存储结构?
3,链表的常用操作代码实现?
1,为什么要使用链表
通过上一篇的学习,我们知道顺序表存在一些问题,主要有以下两个方面。
1,顺序表的长度是固定的,如果超出分配的长度就会造成溢出,如果存放的数据太少则会造成空间浪费。
2,在插入元素和删除元素时(尤其不在尾部时),会移动大量的元素,造成性能和效率低下。
基于以上问题,使用链表可以很好地避免顺序表中出现的问题。这也是我们要使用链表的原因。
2,链表的存储结构
从上图可以看出,单链表中的每个结点都包含一个“数据域”和一个“指针域”。“数据域”中包含当前结点的数据,“指针域”包含下一节点的存储地址,头指针head是指向开始结点的,结束结点没有后继结点,所以结束结点的指针域为空,即null。
3,链表的常用操作及实现代码
链表常用的操作有:
1,插入结点到表头
思路:将head头指针的next指针给新增结点的next,然后将整个新增结点给head头指针的next。因此时间复杂度为O(1)。
示意图:
思路:插入方法与插入到表头一样,只不过多一个步骤就是通过head头指针循环找到终端结点。因此时间复杂度为O(n)。
示意图:
思路:插入方法与插入到表头一样,多循环查找当前结点的动作。因此时间复杂度为O(n)。
示意图:
思路:同插入结点一样,时间复杂度为O(n)。
示意图:
思路:与插入结点和删除结点方法类似,时间复杂度为O(n)。
6,获取链表长度
思路:不像顺序表是连续存储的,获取表的长度非常容易。在链表中,数据不是连续存储的,因此需要循环遍历才能求得链表的长度,所以时间复杂度为O(n)。
下面是具体的实现代码。
C#版:
namespace DS.Model { /// <summary> /// 学生实体 /// </summary> public class Student { public int ID { get; set; } public string Name { get; set; } public int Age { get; set; } } } namespace DS.BLL { /// <summary> /// 封装链表的常用操作 /// </summary> public class ChainListBLL { /// <summary> /// 插入结点在表头 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="head"></param> /// <param name="data"></param> /// <returns></returns> public static Node<T> InsertFirst<T>(Node<T> head, T data) { //创建一个新结点 Node<T> node = new Node<T>(); node.data = data; node.next = head; head = node; return head; } /// <summary> /// 插入结点在表尾 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="head"></param> /// <param name="data"></param> /// <returns></returns> public static Node<T> InsertEnd<T>(Node<T> head, T data) { //创建一个新结点 Node<T> node = new Node<T>(); node.data = data; node.next = null; //空链表直接返回新增的结点 if (head == null) { head = node; return head; } GetLastNode(head).next = node; return head; } /// <summary> /// 插入结点(在包含关键字key的结点之后插入新的结点) /// </summary> /// <typeparam name="T"></typeparam> /// <param name="head"></param> /// <param name="data"></param> /// <returns></returns> public static Node<T> Insert<T,W>(Node<T> head,string key,Func<T,W> where, T data) where W:IComparable { //检查链表是否为空 if (head == null) return null; //查找包含关键字key的结点 if (where(head.data).CompareTo(key) == 0) { Node<T> node = new Node<T>(); node.data = data; node.next = head.next; //注意这里顺序不要弄反了 head.next = node; } Insert(head.next,key,where,data); //用递归继续查找下一个结点 return head; } /// <summary> /// 删除结点(删除包含关键字key的结点) /// </summary> /// <typeparam name="T"></typeparam> /// <typeparam name="W"></typeparam> /// <param name="head"></param> /// <param name="key"></param> /// <param name="where"></param> /// <returns></returns> public static Node<T> Delete<T, W>(Node<T> head, string key, Func<T, W> where) where W : IComparable { if (head == null) return null; //如果只有一个结点 if (where(head.data).CompareTo(key) == 0) { if (head.next != null) head = head.next; //向后移动指针 else return head = null; } else { //判断此结点是否是要删除结点的前一结点 while (head.next != null && where(head.next.data).CompareTo(key) == 0) { head.next = head.next.next; } } Delete(head.next,key,where); //使用递归继续查找 return head; } /// <summary> /// 查找结点(查找包含关键字key的结点) /// </summary> /// <typeparam name="T"></typeparam> /// <typeparam name="W"></typeparam> /// <param name="head"></param> /// <param name="key"></param> /// <param name="where"></param> /// <returns></returns> public static Node<T> GetNodeByKey<T, W>(Node<T> head, string key, Func<T, W> where) where W : IComparable { if (head == null) return null; if (where(head.data).CompareTo(key) == 0) return head; return GetNodeByKey(head.next,key,where); } /// <summary> /// 获取链表长度 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="head"></param> /// <returns></returns> public static int GetLength<T>(Node<T> head) { int count = 0; while (head != null) { head = head.next; //移动指针 count++; } return count; } private static Node<T> GetLastNode<T>(Node<T> head) { if (head.next == null) return head; //最后结点 return GetLastNode(head.next); //使用递归继续查找 } } /// <summary> /// 封装链表 /// </summary> /// <typeparam name="T"></typeparam> public class Node<T> { public T data; //数据 public Node<T> next; //指针 } } namespace ChainList.CSharp { class Program { static void Main(string[] args) { //实例化一个链表对象 Node<Student> node = null; //插入结点在表头 Console.WriteLine("\n****************插入三条数据在表头**********************\n"); node = ChainListBLL.InsertFirst(node, new Student { ID = 1, Name = "a", Age = 10 }); node = ChainListBLL.InsertFirst(node, new Student { ID = 2, Name = "b", Age = 11 }); node = ChainListBLL.InsertFirst(node, new Student { ID = 3, Name = "c", Age = 12 }); Display(node); //插入结点在表尾 Console.WriteLine("\n****************插入一条数据在表尾**********************\n"); node = ChainListBLL.InsertEnd(node, new Student { ID = 4, Name = "d", Age = 13 }); Display(node); //插入结点(在包含关键字key的结点之前插入新的结点) Console.WriteLine("\n*************插入结点(在包含关键字key的结点之后插入新的结点)***********\n"); Console.WriteLine("将ID=5的结点插入到包含'b'关键字的结点之后"); node = ChainListBLL.Insert(node, "b", p => p.Name, new Student { ID = 5, Name = "e", Age = 14 }); Display(node); //删除结点(删除包含关键字key的结点) Console.WriteLine("\n*************删除结点(删除包含关键字key的结点)***********\n"); Console.WriteLine("删除Name='c'的结点"); node = ChainListBLL.Delete(node,"c",p=>p.Name); Display(node); //查找结点(查找包含关键字key的结点) Console.WriteLine("\n*************查找结点(查找包含关键字key的结点)***********\n"); Console.WriteLine("查找Name='d'的结点"); var singNode = ChainListBLL.GetNodeByKey(node, "d", p => p.Name); DisplaySingle(singNode); //获取链表长度 Console.WriteLine("\n*************获取链表长度***********\n"); Console.WriteLine("目前链表中有{0}个结点",ChainListBLL.GetLength(node)); Console.ReadKey(); } /// <summary> /// 展示链表数据 /// </summary> /// <param name="head"></param> private static void Display(Node<Student> head) { Console.WriteLine("\n****************开始展示链表数据**********************\n"); while(head!=null) { Console.WriteLine("ID={0},Name={1},Age={2}",head.data.ID,head.data.Name,head.data.Age); head=head.next; //向后移动指针 } Console.WriteLine("\n****************链表数据展示完成**********************\n"); } private static void DisplaySingle(Node<Student> head) { if (head != null) Console.WriteLine("ID={0},Name={1},Age={2}", head.data.ID, head.data.Name, head.data.Age); else Console.WriteLine("未查找到数据!"); } } }
程序运行结果:
C语言版:
#include "stdio.h" #include "string.h" #include "ctype.h" #include "stdlib.h" #include "io.h" #include "math.h" #include "time.h" #define OK 1 #define ERROR 0 #define TRUE 1 #define FALSE 0 typedef int Status;/* Status是函数的类型,其值是函数结果状态代码,如OK等 */ typedef int ElemType;/* ElemType类型根据实际情况而定,这里假设为int */ typedef struct Node { ElemType data; //数据域 struct Node *next; //指针域 }Node; typedef struct Node *ChainList; /* 定义链表 */ Node *p;//定义一个指向结点的指针变量 ChainList head; //定义指向链表的头指针 /*初始化*/ Status Init(ChainList *head) { //创建头结点,并使头指针指向头结点 *head=(ChainList)malloc(sizeof(Node)); //分配存储地址失败 if (*head==NULL) return ERROR; //指针域为空 (*head)->next=NULL; return OK; } /*插入结点(在以head为头指针的带头结点的链表中第i个结点的位置上插入新的结点s)*/ /************************************************************************/ /* 思路:由于第i个结点的存储地址是存储在第i-1个结点的指针域next中,因此,先使 p指向第i-1个结点,然后生成一个数据域值为e的新结点s,再进行插入操作*/ /************************************************************************/ Status Insert(ChainList head,int i,ElemType e) { int j=0; //声明一结点p指向链表头结点,s表示要插入的新结点 ChainList p,s; p=head; //使p指向新i-1个结点 while(p!=NULL&&j<i-1) { p=p->next; //移动指针 j++; } //判断插入位置是否合法 if (p==NULL||j>i-1) return ERROR; s=(ChainList)malloc(sizeof(Node));//创建新结点 s->data=e; s->next=p->next; p->next=s; return OK; } /*删除结点(在以head为头指针带头结点的链表中删除第i个结点)*/ /************************************************************************/ /* 思路:由于第i个结点的存储地址是存储在第i-1个结点的指针域next中,因此要先使p 指向第i-1个结点,然后使得p->next指向第i+1个结点,再将第i个结点释放掉*/ /************************************************************************/ Status Delete(ChainList head,int i) { int j=0; ElemType e; //声明一结点p指向链表头结点,s表示要插入的新结点 ChainList p,s; p=head; //使p指向第i-1个结点 while(p!=NULL&&j<i-1) { p=p->next;//移动指针 j++; } //删除位置错误 if (p==NULL) return ERROR; else { s=p->next; p->next=s->next; e=s->data; free(s); //free函数释放资源 return OK; } } /*获取链表的长度*/ /************************************************************************/ /* 思路:遍历链表,用计数器计算循环次数*/ /************************************************************************/ int GetLength(ChainList head) { int i=0; //指向第一个结点 ChainList p=head->next; while(p!=NULL) { p=p->next; i++; } return i; } /*获取第i个元素的值*/ /************************************************************************/ /* 思路:由于第i个结点的存储地址是存储在第i-1个结点的指针域next中,因此要先使p 指向第i-1个结点,然后取第i-1个结点的值*/ /************************************************************************/ Status GetDataByIndex(ChainList head,int i,ElemType *e) { int j=0; ChainList p,s; //让p指向第一个结点 p=head->next; while(p!=NULL&&j<i-1) { p=p->next; j++; } if (p==NULL||j>i-1) return ERROR; *e=p->data; return OK; } /*展示数据*/ Status Display(ChainList head) { ChainList p=head->next; printf("\n****开始展示数据****\n"); while(p!=NULL) { printf("%d ",p->data); p=p->next; } printf("\n****展示完毕****\n"); return OK; } void main() { ChainList head; ElemType e; Status i; int j,k; //初始化链表 printf("\n*****************初始化************************\n"); i=Init(&head); printf("初始化后,链表的长度为:%d\n",GetLength(head)); //插入结点(数据) printf("\n*****************插入10条数据************************\n"); for (j=0;j<10;j++) { i=Insert(head,1,j); } printf("插入成功"); Display(head); //删除结点(数据) printf("\n*****************删除第2条数据************************\n"); i=Delete(head,2); printf("删除成功"); Display(head); //查找结点 printf("\n*****************获取第5个结点数据*********************\n"); i=GetDataByIndex(head,5,&e); printf("%d\n",e); //获取链表长度 printf("\n*****************获取链表当前长度**********************\n"); k=GetLength(head); printf("当前长度为%d\n",k); getchar(); }
程序运行结果: