数据结构学习笔记2---链表
数据结构学习笔记2---链表
链表的概念
什么是链表
链表是一种用于存储数据集合的数据结构。 和顺序表共同组成线性表。
链表的特点
- 相邻元素之间通过指针进行连接
- 最后一个元素的后继指针值为NULL
- 在程序执行的过程中,链表的长度可以增加或缩小
- 链表的空间能够按需分配(直到系统内存耗尽)
- 没有内存空间的浪费(但是链表中的指针需要一些额外的内存开销)
几种常见的链表
- 单链表
- 双向链表
- 循环链表
链表与数组的区别
- 对于数组来说,它需要一块连续的内存存储空间来存储,对内存的要求比较高,也就是说,如果我申请100M大小的数组的话,当内存中没有连续的、足够大的存储空间的时候,即便内存中剩余总可用空间大于100M,此时仍然会申请失败
- 对于链表来说,它恰恰相反,它并不需要一块连续的内存空间,它通过“指针”将一组零散的内存块串联起来使用,所以,如果我们申请的是100M大小的链表,当内存中剩余可用空间大于100M的时候,无论是否连续,申请都不会有问题。
单链表的数据结构
//================线性表的单链表存储结构=================
typedef struct LNode {
ElemType data;//数据域
struct LNode *next;//指针域
}LNode,*LinkList;
注:为了提高程序的可读性,在此对同一结构体指针类型起了两个名称:LNode,*LinkList,两者本质上是等价的。
*通常习惯上用 LinkList定义单链表,强调定义的是某个单链表的头指针 ;用 LNode 定义指向单链表中任意结点的指针变量。
头节点
一般情况下,为了处理方便,在单链表的第一个结点之前附设一个结点,称之为头结点。
首元结点、头结点、头指针三个容易混淆的概念 :
单链表
单链表是最重要的一种链表,也是其他链表的基础,接下来作重点介绍。
单链表初始化
算法步骤 :
- 生成新结点作为头结点,用头指针L 指向头结点。
- 头结点的指针域置空。
算法描述 :
Status InitList(LinkList *&L){
L=new LNode;
L->data=data;
L-next=NULL;
return OK;
}
取值
算法步骤 :
-
用指针p指向首元结点,用j做计数器初值赋为1。
-
从首元结点开始依次顺着链域 next 向下访问,只要指向当前结点的指针 p 不为空 (NULL), 并且没有到达序号为i的结点,则循环执行以下操作:
• p指向下一个结点;
• 计数器j就相应加1。
-
退出循环时, 如果指针p为空, 或者计数器j大于i, 说明指定的序号i值不合法(i大于
表长n或i小于等于0), 取值失败返回ERROR; 否则取值成功, 此时j=i时,p所指的结点就
是要找的第i个结点,用参数e保存当前结点的数据域, 返回OK。
算法描述 :
Status GetElem(iLinkList L,int i,ElemType &e){
p=L->next;
j=1;
while(p!=NULL&&j<i){
p=p->next;
j++;
}
if (!p||j>i){ //!p说明查询范围过大,j>i对应i<0的情况
return ERROR;
}
e=p->data;
return OK;
}
单链表取值算法的平均时间复杂度为O(n)
查找
算法步骤 :
- 用指针p指向首元结点 。
- 从首元结点开始依次顺着链域next向下查找, 只要指向当前结点的指针p不为空, 并且p所指结点的数据域不等千给定值e, 则循环执行以下操作: p指向下一个结点 。
- 返回p。若查找成功,p此时即为结点的地址值,若查找失败,p的值即为NULL。
算法描述 :
LNode *LocateELem(LinkList L, Elemtype e){
//在带头结点的单链表L中查找值为e的元素
p=l->next;
while(p&&p->data!=e){
p=p->next;
}
return p;
}
插入
核心语句:s->next=p->next; p->next=s;
算法描述 :
Status Listinsert(LinkList &L,int i,ElemType e){
//在单链表L中的第i个位置插入元素e
//1.创建一个新节点
s=new LNode;
//2.找到第i-1的节点的位置
p=L;
j=0;
//注:这里的注释条件和前开你查找是一样了,虽然p节点和j的指向仍然一致,但他们的七点整体前移了1,这是因为要用做后面的异常判断。
while(p&&j<i-1){
p=p->next;
j++;
}
if (!p||j>i-1) return ERROR; ///i>n+l或者i<1
s-data=e;
s->next=p->next;
p->next=s;
return OK;
}
删除
核心语句:s=p->next; p->next=p->next->next; free(s);
算法描述 :
Status ListDelete(LinkList &L,int i){
//删除链表L第i个节点
//1. 找到第i-1个节点
p=L;
j=0;
while(p->next&&j<i-1){
p=p->next;
j++;
}
if(!(p->next)||j>i-1) return ERROR; //当i>n或i<1时,删除位置不合理
s=p->next;
p->next=p->next->next;
free(s);
return OK;
}
创建单链表
前插法
算法步骤 :
- 创建一个只有头结点的空链表。
- 根据待创建链表包括的元素个数n, 循环n次执行以下操作:
-
生成一个新结点*p;
-
输入元素值赋给新结点*p的数据域;
-
将新结点*p插入到头结点之后。
算法描述 :
void CreateList_H(LinkList &L,int n){
L=new LNode;
L->next=NULL; //先建立一个带头结点的空链表
for(i=0;i<n;i++){
p=new LNode;
cin>>p->data;
p->next=L->next;
L->next=p;
}
}
时间复杂度亦为O(n)
尾插法
算法步骤 :
- 创建一个只有头结点的空链表。
- 尾指针r初始化, 指向头结点。
- 根据创建链表包括的元素个数n, 循环n次执行以下操作:
- 生成一个新结点*p;
- 输入元素值赋给新结点*p 的数据域;
- 将新结点p 插入到尾结点r之后;
- 尾指针r指向新的尾结点*p。
算法描述 :
void CreateList_R(LinkList &L,int n){
L=new LNode;
L->next=NULL;
r=L; //尾指针r指向头结点
for(i=0;i<n;i++){
p=new LNode;
cin>>p->data;
p->next=NULL;
r->next=p;
r=p;
}
}
时间复杂度亦为O(n)
c语言实现单链表
#include <stdio.h>
#include <stdlib.h>
typedef struct Link {
int elem;
struct Link *next;
}link;
link * initLink();
//链表插入的函数,p是链表,elem是插入的结点的数据域,add是插入的位置
link * insertElem(link * p, int elem, int add);
//删除结点的函数,p代表操作链表,add代表删除节点的位置
link * delElem(link * p, int add);
//查找结点的函数,elem为目标结点的数据域的值
int selectElem(link * p, int elem);
//更新结点的函数,newElem为新的数据域的值
link *amendElem(link * p, int add, int newElem);
void display(link *p);
int main() {
//初始化链表(1,2,3,4)
printf("初始化链表为:\n");
link *p = initLink();
display(p);
printf("在第4的位置插入元素5:\n");
p = insertElem(p, 5, 4);
display(p);
printf("删除元素3:\n");
p = delElem(p, 3);
display(p);
printf("查找元素2的位置为:\n");
int address = selectElem(p, 2);
if (address == -1) {
printf("没有该元素");
}
else {
printf("元素2的位置为:%d\n", address);
}
printf("更改第3的位置上的数据为7:\n");
p = amendElem(p, 3, 7);
display(p);
return 0;
}
link * initLink() {
link * p = (link*)malloc(sizeof(link));//创建一个头结点
link * temp = p;//声明一个指针指向头结点,用于遍历链表
//生成链表
for (int i = 1; i < 5; i++) {
link *a = (link*)malloc(sizeof(link));
a->elem = i;
a->next = NULL;
temp->next = a;
temp = temp->next;
}
return p;
}
link * insertElem(link * p, int elem, int add) {
link * temp = p;//创建临时结点temp
//首先找到要插入位置的上一个结点
for (int i = 1; i < add; i++) {
temp = temp->next;
if (temp == NULL) {
printf("插入位置无效\n");
return p;
}
}
//创建插入结点c
link * c = (link*)malloc(sizeof(link));
c->elem = elem;
//向链表中插入结点
c->next = temp->next;
temp->next = c;
return p;
}
link * delElem(link * p, int add) {
link * temp = p;
//遍历到被删除结点的上一个结点
for (int i = 1; i < add; i++) {
temp = temp->next;
if (temp->next == NULL) {
printf("没有该结点\n");
return p;
}
}
link * del = temp->next;//单独设置一个指针指向被删除结点,以防丢失
temp->next = temp->next->next;//删除某个结点的方法就是更改前一个结点的指针域
free(del);//手动释放该结点,防止内存泄漏
return p;
}
int selectElem(link * p, int elem) {
link * t = p;
int i = 1;
while (t->next) {
t = t->next;
if (t->elem == elem) {
return i;
}
i++;
}
return -1;
}
link *amendElem(link * p, int add, int newElem) {
link * temp = p;
temp = temp->next;//tamp指向首元结点
//temp指向被删除结点
for (int i = 1; i < add; i++) {
temp = temp->next;
}
temp->elem = newElem;
return p;
}
void display(link *p) {
link* temp = p;//将temp指针重新指向头结点
//只要temp指针指向的结点的next不是Null,就执行输出语句。
while (temp->next) {
temp = temp->next;
printf("%d ", temp->elem);
}
printf("\n");
}
结果:
初始化链表为:
1 2 3 4
在第4的位置插入元素5:
1 2 3 5 4
删除元素3:
1 2 5 4
查找元素2的位置为:
元素2的位置为:2
更改第3的位置上的数据为7:
1 2 7 4
生活是一首长长的歌!