定义
线性表的链式存储又称单链表,它是指通过任意的存储单元来存储线性表的数据。注意此时的数据在物理地址上不在连续,内存是动态分配的,而且数据是存放在结点中,结点组成链表,每个节点分为数据域和指针域,所以我们在定义的时候,数据域来存放数据,指针域存放后面结点的地址(因为地址不连续。必须有一个变量来知道下一个结点在哪里)
代码描述:
typedef int ElemType;
typedef struct LNode {ElemType data;struct LNode* Next;
}LNode,*LinkList;
特点
- 可以解决顺序表需要大量连续存储空间的缺点
- 单链表有指针域,我们只要存储数据,现在又存储了个指针,浪费了内存
- 知道第i个位置,就知道后面所有的结点内容了,但不能知道前面的结点
基本操作
再说基本操作之前,我们先来了解头指针和头结点
- 头指针:标识一个单链表
- 头结点:第一个结点之前附加的一个结点,可有可无。头结点的数据域可以不设置任何信息,指针域指向第一个元素结点,就是数据域有存放我们要的数据的结点
- 区别:头指针就是链表的名字,指向第一个结点,这个理解的时候,可以联想数组,数组名就是头指针。如果有头结点,假如数组名叫a,a[1]就是头结点,只是这个里面不存放数据,头指针指向头结点,头指针的值就是头结点的首地址,,头结点的指针域的值是存放第一个数据的首地址
1、建立单链表:
1.1头插法
假如现在链表只有一个数据是1,按照头插法在插入数据2,那么现在的数据是2,1,第一个数据是2,这就是头插法
/*
头插法建立单链表,将新的数据插入当前链表的表头
*/LinkList List_HeadInsert(LinkList& L) {LNode* s; int x;L = (LinkList)malloc(sizeof(LNode)); //创建头结点L->next = NULL;scanf("%d", &x);while (x != 9999) //输入9999表示结束{s = (LNode*)malloc(sizeof(LNode)); //创建新的结点s->data = x;s->next = L->next;L->next = s;scanf("%d", &x);}return L;
}
我们知道头结点L是第一个结点,里面不存放数据,所以我们在插入数据s时,只需将头结点L的指针域的值赋值给新插入数据S的指针,这样新插入的数据S就指向头指针L指向的结点,现在新插入的结点s和头结点L都指向了一个结点,相当于两个头结点了,但头结点只能有一个,所以我们的L还要指向S,始终保证L都是第一个节点,s就是第一个带有我们存放数据的结点
1.2 尾插法
/*
尾插法:在尾部插入数据
*/
LinkList List_TailInsert(LinkList& L) {int x;L = (LinkList)malloc(sizeof(LNode));LNode* s, * r = L; //r为表尾指针scanf("%d", &x);while (x != 9999) //输入9999表示结束{s = (LNode*)malloc(sizeof(LNode));s->data = x;r->next = s;r = s;scanf("%d", &x);}r->next = NULL;return L;
}
此时多了个r指针,r始终表示最后一个结点,它的值是最后一个结点的首地址,我们插入一个新结点s时,尾插法要把s放到数据最后,没有插入s前,r表示最后一个结点,所以让r的指针域指向s,r现在是倒数第二个结点,但r始终表示最后一个结点,所以要把r指向s
1.3 尾插法和头插法
- 尾插法生成的链表结点的次序与输入数据的顺序相同,而头插法不一致
- 每个结点插入的时间为0(1),链表表长为n,则时间复杂度为0(n)
- 头插法的L始终表示头结点,尾插法的r始终要表示最后一个结点
2、按序号查找结点值
思路:从第一个节点出发,顺指针next依次往下找,当找到第i个结点结束,否则返回null
/*按序号查找结点值:
*/
LNode* GetElem(LinkList L, int i) {int j = 1; //计数LNode* p = L->next;if (i <= 0)return NULL;while (p && j < i) {p = p->next;j++;}return p;
}
头结点为第一个结点,不存放值,所以不必要查找,直接j=1就行,为什么要p&&j<i呢?因为我们在创建我们的链表时,没有定义变量来记录长度,所以我们就不知道链表的长度,当j<i,但已经超过链表的长度时,我们就不能查找下去了,刚好最后一个结点的next是null,能判断有没有到达最后一个结点
3、按值查找
返回第一个等于我们要查找值的结点
/*按照值来查找
*/LNode* LocateElem(LinkList L, ElemType e) {LNode* p = L->next;while (p != NULL && p->data!=e){p = p->next;}return p;
}
4、插入结点
将数据e插入到第i个结点上,首先判断i的合法性
/*
插入到第i个位置
*/bool Insert(LinkList& L, int i,ElemType e) {LNode* p = GetElem(L, i - 1);if (p != NULL) {LNode* s = (LNode*)malloc(sizeof(LNode));s->data = e;s->next = p->next;p->next = s;return true;}else{return false;}}
首先查找第i-1位置结点,为什么不是i呢?因为插入的时候,相当于第i个位置后移一步,所以第i-1的next要指向新插入的i位置,新插入的i位置要指向旧的i位置结点,如果查找i,就没办法知道i-1位置。根据查找i-1位置来判断i的合法性。如果不合法返回false
5、删除操作
将单链表的第i个结点删除。
思路:先判断删除位置的合法性,然后查找第i-1个结点,删除第i位置
/*
删除操作
*/bool Delete(LinkList& L, int i) {LNode* p = GetElem(L, i - 1);if (p != NULL) {LNode* q;q = p->next;p->next = q->next;free(q);return true;}else{return false;}
}