线性表的链式存储结构
线性表的实现分顺序存储结构和链式存储结构。
线性表的链式存储结构又称单链表。
上一节我们学习了线性表的顺序存储结构,并实现解顺序存储的基本操作。
这一节我们来学习线性表链式存储结构,那我们再想象一下我为什么我们要引入链式存储结构,万物存在必有其道理
主要还是因为线性存储结构存在着这样一个问题:当我们需要插入和删除元素时,就必须挪动大量与之无关的元素,因为线性存储结构结点与节点之间的关系是相邻关系,一个节点挨着一个节点
如为了插入或者删除一个元素移动大量的元素,这样就降低了程序运行效率。
当我们引入
顾名思义链式存储结构,数据与数据之间是以链式关系来产生连接的的,我们可以脑补一下锁链的样子,不扯淡了,进入正题--
我们如何定义一个链式存储结构的节点呢?
/*Node表示一个节点*/ typedef struct Node{ int data; //数据域 struct Node* next; //存放下一个节点的指针 }Node; typedef struct Node* LinkList; /*取了一个别名,定义LinkList = Node*,用于存放节点的指针*/
一个节点包括一个数据域和指针域
我们将这种只带有一个指针域的线性表称为单链表。
链表中第一个结点的存储位置叫做头指针。
单链表的第一个结点前附设一个结点,称为头结点。
注意可以没有头节点,但是要是链表,就一定存在头指针。
那么问题来了,我们如何区分头节点和头指针呢?
头指针:是指向链表的指针,如果不存在头节点,那么头指针就会指向链表的第一个节点。
头节点:实际上是不存在的,只不过是为了链表的一些操作方便而设置的,头节点与第一个节点以链式关系相连,并且头节点的数据域没有意义,指针域存放第一个节点的地址。
单链表的插入
实现代码:
/*插入元素 n是位置,c是数*/ void InsertElemList(LinkList L,int n,int c){ int count=0; LinkList p,s; p =L; //注意这里的p不在指向第一个节点了 count =1; while(p->next && count<n){ 9 p =p->next; ++count; } s =(Node*)malloc(sizeof(Node)); s->data=c; s->next =p->next; p->next=s; }
主要思路:
1、创建一个节点指针,用于存放头节点地址,注意这里不是第一个节点。
2、将这个节点移动要插入节点的位置上(插在第几个位置上就移动几个位置)
3、然后创建一个要插入的节点并分配内存空间
4、执行s->next =p->next; p->next=s; //注意前后顺序不能调
单链表的删除
主要代码:
/*删除元素*/ void DeleteElem(LinkList L,int n){ int count=0; LinkList p ,q; p =L; //注意这里的p不在指向第一个节点了 count =1; while(p->next && count<n){ // p =p->next; ++count; } if(!(p->next) || count>n) printf("没有找到可删除的元素"); q= p->next; p->next = q->next; free(q); }
主要思路:
1、定义一个计数器(用于确定删除的位置)
2、创建一个指针p,指向头节点
3、创建一个指针q(用于释放内存)
4、当指针移动到要删除元素的前一个,停止
5,执行q= p->next; p->next = q->next; free(q);
单链表的建表(头插法)
顾名思义直接插在第一位,就是头节点的后面。
void CreateListHead(LinkList *L,int n){ LinkList p; *L = (Node*)malloc(sizeof(Node)); //生成的新节点节点要初始化 (*L)->next=NULL; //并指向空 for(int i=0;i<n;i++){ p =(Node*)malloc(sizeof(Node)); //新生成的节点节点要初始化 p->data=i; p->next=(*L)->next; //这里不能指向NULL (*L)->next =p; //两级指针 } }
主要思路:
1、首先创建头节点,并初始化指向空
2、然后再头节点和NULL之间插入第一个元素,然后再头节点和第一个元素插入第二个元素,以此类推...
3、这里要注意的是这里节点的地址要使用二级来存储,因为我们在使用的malloc为链表初始化分配的内存空间在第一次函数调用后内存就失效了
也可以不使用二级指针,直接把初始化链表的代码另外写在外面即可
4、所以用可以使用二级指针来避免内存泄漏
单链表的建表(尾插法)
void CreateListTail(LinkList *L ,int n){ LinkList p,r; //生成节点p,存放Node地址 *L = (Node*)malloc(sizeof(Node)); //生成的头节点节点要初始化 (*L)->next=NULL; r = *L; //用于遍历 for(int i=0;i<n;i++){ p =(Node*)malloc(sizeof(Node)); //新生成的节点节点要初始化 p->data=i; r->next=p; r=p; //r指针移动到p上r ,以便 } r->next=NULL; //最后一个指向空 }
主要思路:
1、定义两个指针,一个r,一个q
2、p用于创建新的节点,r用于确定尾部,这样每一次插入的时候只要确定r的位置,就确定了尾部的位置
3、二级指针的使用同头插法
4、核心代码执行 r->next=p; r=p; r->next=NULL;
单链表的遍历
void TraverseList(LinkList L){ LinkList p; p = L->next; while(p){ printf("%d ",p->data); p=p->next; } }
清空单链表
/*清空链表*/ void ClearList(LinkList L){ LinkList p ,q; p = L->next; //指向第一个元素 while(p){ q=p->next; //q指向了p的下一个 free(p); //释放内存 p =q; } L->next =NULL; //头节点指向空 }
主要思路:
1、定义一个指针指向第一个节点
2、在定义一个指针用于间接存放即将释放的内存,用q表示
3、当p移动到要释放节点内存时,将它赋值给q,然后移动到下一位,依次类推...
单链表的初始化
主要代码
/*初始化链表,传的是二级指针,操作的是一级指针的地址*/ int InitList(LinkList *L){ *L=(LinkList)malloc(sizeof(Node)); //这里的*L就是Node节点的指针对象 if(!(*L)) //申请内存失败 return 0; (*L)->next=NULL; return 1; }
也没有什么,一定要注意在malloc分配的内存在函数调用完成后就会失效,如果想继续使用,要么借助C++里面的引用,或者二级指针(可能还有其它办法)
因为内存泄漏问题被搞了一个晚上了 - -
罪过--罪过--
指针作为参数传值可以参考大佬资料:https://www.cnblogs.com/WeyneChen/p/6672045.html
其它的一下操作见下面代码吧
#include <stdio.h> #include <stdlib.h> #include "time.h" /*Node表示一个节点*/ typedef struct Node{ int data; struct Node* next; }Node; typedef struct Node* LinkList; /*定义LinkList*/ /*初始化链表*/ int InitList(LinkList *L){ *L=(LinkList)malloc(sizeof(Node)); //这里的*L就是Node节点的指针对象 if(!(*L)) //申请内存失败 return 0; (*L)->next=NULL; return 1; } //头插法,n表示插入的个数 void CreateListHead(LinkList *L,int n){ LinkList p; *L = (Node*)malloc(sizeof(Node)); //生成的新节点节点要初始化 (*L)->next=NULL; //并指向空 for(int i=0;i<n;i++){ p =(Node*)malloc(sizeof(Node)); //新生成的节点节点要初始化 p->data=i; p->next=(*L)->next; //这里不能指向NULL (*L)->next =p; //两级指针 } } //尾插法,n表示插入的个数 void CreateListTail(LinkList *L ,int n){ LinkList p,r; //生成节点p,存放Node地址 *L = (Node*)malloc(sizeof(Node)); //生成的头节点节点要初始化 (*L)->next=NULL; r = *L; //用于遍历 for(int i=0;i<n;i++){ p =(Node*)malloc(sizeof(Node)); //新生成的节点节点要初始化 p->data=i; r->next=p; r=p; //r指针移动到p上r ,以便 } r->next=NULL; //最后一个指向空 } /*遍历链表*/ void TraverseList(LinkList L){ LinkList p; p = L->next; while(p){ printf("%d ",p->data); p=p->next; } } /*清空链表*/ void ClearList(LinkList L){ LinkList p ,q; p = L->next; //指向第一个元素 while(p){ q=p->next; //q指向了p的下一个 free(p); //释放内存 p =q; } L->next =NULL; //头节点指向空 } /*获取链表长度*/ int GetLengthList(LinkList L){ LinkList p; p=L->next; int count=0; //计数器 while(p){ count++; p= p ->next; } return count; } /*删除元素*/ void DeleteElem(LinkList L,int n){ int count=0; LinkList p ,q; p =L; //注意这里的p不在指向第一个节点了 count =1; while(p->next && count<n){ // p =p->next; ++count; } if(!(p->next) || count>n) printf("没有找到可删除的元素"); q= p->next; p->next = q->next; free(q); } /*插入元素 n是位置,c是数*/ void InsertElemList(LinkList L,int n,int c){ int count=0; LinkList p,s; p =L; //注意这里的p不在指向第一个节点了 count =1; while(p->next && count<n){ // p =p->next; ++count; } s =(Node*)malloc(sizeof(Node)); s->data=c; s->next =p->next; p->next=s; } /* 初始条件:顺序线性表L已存在 */ /* 操作结果:返回L中第1个与e满足关系的数据元素的位序。 */ /* 若这样的数据元素不存在,则返回值为0 */ int LocateElem(LinkList L,int e){ LinkList p; p =L->next; while(p){ if(p->data == e) return 1; p =p->next; } return 0; } /*获取元素,n表示第几个,并返回查到的值*/ int GetElem(LinkList L,int n){ LinkList p; //生成节点p,存放Node地址 p = L->next; //p指向第一个元素 int count=1; //计数器 while(p && count<n){ //查找 p = p->next; count++; } if(!p || count>n){ //没找到 return 0; } int ret = p->data; //找到 return ret; } /*判断链表是否为空*/ int ListElmpty(LinkList L){ if(L->next){ return 0; }else{ return 1; } } int main(){ LinkList L1; //创建一个节点,用于头插法 LinkList L2; //创建一个节点,用于尾插法 printf("......头插法...... "); InitList(&L1); //初始化 CreateListHead(&L1,5); TraverseList(L1); printf(" "); printf("......尾插法...... "); InitList(&L2); //初始化 CreateListTail(&L2,5); TraverseList(L2); printf(" "); //获取元素的值 int getElem= GetElem(L2,3); printf("%d ",getElem); //获取长度 int GetLength=GetLengthList(L2); printf("L1链表的长度:%d",GetLength); printf(" "); //删除L1中2号元素 printf("删除L1中2号元素:"); DeleteElem(L1,2); TraverseList(L1); printf(" "); //在第三个位置插入11 printf("在第三个位置插入11元素:"); InsertElemList(L1,3,11); TraverseList(L1); printf(" "); int localFind=LocateElem(L1,11); printf("找到了吗: %d d",localFind); //判断L1是否为空 int lstElempty=ListElmpty(L1); printf("L1为空吗: %d ",lstElempty); //清空L1 ClearList(L1); //在判断L1是否为空 lstElempty=ListElmpty(L1); printf("L1为空吗: %d ",lstElempty); return 0; }
写完插入和删除操作,我们便可以看出,链式存储结构对于插入和删除的优势是明显的,不需要进行大量的元素的移动。
当然单链表这么个优秀,也是存在缺点的
缺点就是其不便于进行查找和修改,每查找或者修改一个元素就要开始从头开始遍历 - -这么坑爹的吗 ?没错 就是这么坑爹 - -
所以当我们应用的场合不同 ,就用不同的存储结构。