在介绍链表之前,我们首先来了解一下线性表。线性表是最基本、最简单、也是最常用的一种数据结构。线性表中数据元素之间的关系是一对一的关系,即除了第一个和最后一个数据元素之外,其它数据元素都是首尾相接的。线性表的逻辑结构简单,便于实现和操作。因此,线性表这种数据结构在实际应用中是广泛采用的一种数据结构。
链表是一种最常见的线性表,栈、队列是两种特殊的线性表。
下面,我们开始学习“顺序存储的线性表”和“链式存储的线性表”。
(1)顺序存储线性表
1、采用数组来存储顺序线性表的各元素。
2、基本结构:
- #define MAXSIZE 20
- typedef int Elemtype;
- typedef struct
- {
- Elemtype data[MAXSIZE]; //采用数组存储线性表
- int length; //线性表的长度,即线性表元素的个数
- }sqList;
3、数组的长度(最大线性表长度)大小不能够改变,但是线性表的长度(元素的个数)可以改变。
4、通过下标索引可以直接获取顺序线性表中的各个元素。
5、插入:判断下标是否越界、判断是否在最后插入,元素后移、插入元素;
删除:取出删除的元素(参数是指针形式)、判断是否在最后删除,元素前移。
6、插入、删除时需要移动大量的元素;当线性表长度过长时,难以确定存储空间的容量(数组大小)。
(2)链式存储线性表
1、结点:数据域+指针域
链表:n个结点链接成一个链表(a1->a2->a3->……->an)。
2、头指针:链表中第一个结点的存储位置(特别注意:不是第一个节点中的指针,而是指向第一个结点的一个指针)。有时候,为了方便对链表进行操作,我们也会在第一个结点前附加一个结点,作为“头结点”,但头结点的数据不能够存储任何数据。头指针具有标识的作用,通常会将其视为链表的名字。
尾指针:最后一个结点的指针域,此指针为空(NULL)。
关于“头指针”和“头结点”,可以参考资料:http://blog.csdn.net/passball/article/details/6125767,这里讲的很透彻。
3、单链表存储结构:
- typedef int Elemtype;
- typedef struct ListNode
- {
- Elemtype data;
- struct ListNode *next;
- };
- typedef struct ListNode *LinkList; //基础理解:这里已经将LinkList定义成了一个指针类型了,因此,如果定义一个链表指针时,就不能再加“*”了,直接用:LinkList p, q;而不是像其他参考资料:LinkList *p,*q;
另外,很多参考数据上,还有许多其他的形式;但数据结构是一样的,只是写法不一样,如:
- struct ListNode
- {
- int data;
- struct ListNode * next;
- }Node, *ListNode; //在使用的过程中,要区分使用Node(非指针)和LinkList(指针)
4、单链表的基本操作
1)读取单链表中的元素
- //取出单链表L(头指针)中第i个结点中的数据,并存储在e中
- bool GetElem(LinkList L, int i, Elemtype *e)
- {
- int n=1;
- LinkList p;
- p = L->next;//p指向第一个结点(第一个元素所在的结点),不是头结点
- while(p && n < i)
- {
- p = p->next;
- n++;
- }
- if(!p || n > i)
- return 0;
- *e = p->data;//是*e,而不是e;参数中,采用的是指针形式,这样就能达到存储的效果
- return 1;
- }
2)单链表的插入
- //在单链表L中第i个位置之前插入新的数据元素e
- bool ListInsert(LinkList *L, int i, Elemtype e)//形参,采用了指针传参(LinkList本身就是指针,再加上*L后,起到了指针传参的作用),但是函数体中使用时,一定要记得(*L)一起使用,而不是像一些资料中的直接用L;当然这里可以改用引用传参,写成LinkList &L即可,这样的话,函数体中使用时就不用再加*了,调用这个函数时,也直接用L(头指针)就行了
- {
- int n;
- LinkList p, s;//s用来存储数据元素e,此时s不是结点,而是指针
- p = (*L) -> next;
- while(p && n < i)
- {
- p = p->next;
- n++;
- }
- if(!p || n>i)
- return 0;
- s =(LinkList)malloc(sizeof(Node));//易错:计算的是Node结点的长度,而不是LinkList(指针)的长度
- s->data = e;
- s->next = p->next;
- p->next = s;
- return 1;
- }
3)单链表结点的删除
- //删除单链表L中第i个数据元素,并用e返回其值
- bool ListDelete(LinkList *L, int i, Elemtype *e)
- {
- int n = 1;
- LinkList p, q;
- p = (*L)->next;
- while(p && n<i)
- {
- p = p->next;
- n++;
- }
- if(!p || n>i)
- return 0;
- q = p->next;
- p->next = q->next;
- *e = q->data;
- free(q);
- return 1;
- }
4)单链表的创建
- /*使用*L的原因:指针传参-为了让创建后的链表任然能够传递到主函数中,否则会有错误*/
- void CreatList(LinkList *L, int i)
- {
- LinkList p;
- int n=1;
- *L = (LinkList)malloc(sizeof(Node));
- (*L)->next = NULL;
- while(n++ <= i)
- {
- p = (LinkList)malloc(sizeof(Node));
- p->data = rand()%100;
- p->next = (*L)->next;
- (*L)->next = p;
- }
- }
5)显示链表(遍历链表)
- void ShowList(LinkList L)
- {
- LinkList p;
- p = L->next;
- while(p)
- {
- printf("%d ",p->data);
- p = p->next;
- }
- }