代码基本来源:2020王道数据结构(侵删)
每个结点除了存放数据元素外,还要存储指向下一个结点的指针
- 优点:不要求大片连续空间,改变容量方便
- 缺点:不可随机存取,要耗费一定空间存放指针
LNode * 与 LinkList 本质上都是指向LNode类型的指针
typedef struct LNode{//定义单链表结点类型
ElemType data;//数据域 每个结点存放一个数据元素
struct LNode *next;//指针域 指向下一个结点
}LNode, *LinkList;
- 第一个结点存储数据(第一个结点即为链表位序为1的结点)
防止脏数据(指向莫名其妙的区域)
bool InitList(LinkList &L){
L = NULL;//空表,暂时还没有任何结点
return L;
}
bool IsEmpty(LinkList L){
return (L == NULL);
/* 等价于下面的代码
if (L == NULL)
return true;
else
return false;
*/
}
bool ListInsert(LinkList &L,int i, ElemType e){
if (i<1)
return false;//插入位置非法
if (i == 1){//插入第一个结点的操作与其他结点操作不同
LNode *t = (LNode *)malloc(sizeof(LNode));
t->data = e;//填充新结点
t->next = L->next;
L = t;//头指针指向新结点
return ture;
}
LNode *p;//p指向当前扫描到的结点
p = L;//p指向当前的头结点,认为是第0个结点
int j = 1;//当前p指向的是第j个结点
while ( (p != NULL) && (j < i-1)){//循环找到第i-1个结点
p = p->next;
++j;
}
if (p == NULL) //找不到第i-1个结点
return false;
//找到第i-1个结点,插入结点
LNode *t = (LNode *)malloc(sizeof(LNode));
t->data = e;//填充新结点
t->next = p->next;
p->next = t;
return ture;//插入成功
}
- 头结点不存储数据(链表第一个结点不存储数据)
- 头指针指向的结点的下一个结点才开始存放数据
bool InitList(LinkList &L){
L = (LNode *)malloc(sizeof(LNode));//分配一个头结点
if (L == NULL) //内存不足,分配失败
return false;
L->next = NULL;//头结点之后暂时还没有结点
return true;
}
bool IsEmpty(LinkList L){
if (L->next == NULL)
return true;
else
return false;
}
bool ListInsert(LinkList &L,int i, ElemType e){
if (i<1)
return false;//插入位置非法
//下面这段代码相当于查找第i-1个结点的操作
//LNode *p = GetElem(L,i-1)
LNode *p;//p指向当前扫描到的结点
p = L;//p指向当前的头结点,认为是第0个结点
int j = 0;//当前p指向的是第j个结点
while ( (p != NULL) && (j < i-1)){//循环找到第i-1个结点
p = p->next;
++j;
}
//下面的操作相当于后插操作 可调用后插函数实现
// return InsertNextNode(p,e)
if (p == NULL) //找不到第i-1个结点
return false;
//找到第i-1个结点,插入结点
LNode *t = (LNode *)malloc(sizeof(LNode));
t->data = e;//填充新结点
t->next = p->next;
p->next = t;
return ture;//插入成功
}
- 对于单链表,给定结点之后的区域是可知区域,而前面的区域是不可知区域
- 给点结点的前插操作和后插操作不相同
bool InsertNextNode(LNode *p,ElemType e){
if (p == NULL)//边界条件判断
return false;
LNode *t = (LNode *)malloc(sizeof(LNode));
if ( s == NULL)//内存不足,分配失败
return false;
t->data = e;//填充结点
t->next = p->next;
p->next = t;//将结点t连到p之后
return true;
}
给定结点的前插操作(给定插入的数据)
bool InsertPriorNode(LNode *p,ElemType e){
if (p == NULL)//边界条件判断
return false;
LNode *t = (LNode *)malloc(sizeof(LNode));
if ( s == NULL)//内存不足,分配失败
return false;
t->data = p-data;//将p中元素复制到t中
p->data = e;//p中元素覆盖为e
t->next = p->next;
p->next = t;//将结点t连到p之后
return true;
}
删除表L中第i个位置的元素,并用e返回删除元素的值
bool ListDelete(LinkList &L, int i,ElemType &e){
if (i<1)
return false;
//下面这段代码相当于查找第i-1个结点的操作
//LNode *p = GetElem(L,i-1)
LNode *p;//指针p指向当前扫描到的结点
int j = 0;//当前p指向的是第几个结点
p = L;//L指向头结点,头结点假设是第0个结点(不存数据)
while( p != NULL && j<i-1 ){//寻找第i-1个结点
p = p->next;
++j;
}
if (p == NULL)//找不到第i-1个结点
return false;
if (p->next == NULL)//第i-1个结点后已无其他结点(删除第i个位置的结点非法)
return false;
LNode t = p->next;//临时存放所需删除的结点
e = t->data;//引用 需返回e的值
p->next = t->next;//将所需删除结点在链表中断开
free(t);//释放结点的存储空间
}
- 思路1:传出头指针,循环寻找p的前驱结点(O(n))
- 思路2:偷天换日(类似于结点前插)
- 删除指定结点无法就是修改当前结点的前驱结点指向当前结点的后继结点
- 关键点
- 将当前结点的后继结点的数据覆盖到当前结点上,然后再修改当前结点的指向
- 有坑:不要忘记判断p->next是否为NULL(指点结点为表尾)(每一次使用指针的时候都要想一下是否为NULL)————这种情况只能从头开始依次寻找p的前驱结点O(n)
bool DeleteNode(LNode *p){
if ( p == NULL)
return false;
LNode *q = p->next;//指向当前结点p的后继结点
if (q == NULL){//删除结点在表尾
Find
}
p->data = p->next->data;//(和后续结点交换数据域)将当前结点p的下一结点的数据覆盖到当前结点
p->next = q->next;//将*q所指向结点从链表断开
free(q);//释放后继结点的存储空间
return true;
}
按位查找:获取表L中指向第I个位置结点的指针
- 成功:返回指向该结点的指针
- 失败(注意):返回NULL
- 查找位置不合法(i<0)
- i = 0 返回指向头结点的指针
- i = 1,2,… 返回指向数据结点的指针
- 查找位置不合法(查找位置超出链表长度)
//按位查找 带头结点(第0个结点)
LNode *GetElem(LinkList L,int i){
if (i<0)
return NULL;
LNode *p;//指针p指向当前扫描到的结点
int j = 0;//当前p指向的是第j个结点
p = L;//指针p指向头结点,头结点是第j个结点(不存数据)
while(p != NULL && j < i){//循环找到第j个结点
p = p->next;
++j;
}
return p;
}
按值查找:在表L中查找具有给定关键字值的元素
//按值查找,找到数据域==e的结点
LNode * LocateElem(LinkList L,ElemType){
LNode *p = L->next;
//从第一个结点开始查找数据域为e的结点
while( p != NULL && p-data != e){
p = p->next;
}
return p;//调用程序需判断是否为NULL
}
思路:遍历链表时加一个计数器
//求表的长度
int Length(LinkList L){
int len = 0;
LNode *p = L-next;//p指向链表的第一个结点
while (p != NULL){
++len;
p = p->next;
}
return len;
}
- 初始化一个单链表
- 每次取一个数据元素,插入到表尾/表头
综上:利用后插的思想,每次将结点插到尾指针所指结点之后(最终方法)
LinkList List_TailInsert(LinkList &L){
int x;//设ElemType为整形
LNode *s,*r = L;//s指向临时插入的新结点,r为表尾指针
scanf("%d",&x);//输入结点的值
while( x != -1 ){//表示结束的值
s = (LNode *)malloc(sizeof(LNode));
s->data = x;
r->next = s;
r = s;//指向新的表尾结点
scanf("%d",&x);//读入新的数据
}
r->next = NULL;//尾结点指针置空
return L;//返回表头指针
}
LinkList List_TailInsert(LinkList &L){
int x;//设ElemType为整形
scanf("%d",&x);//输入结点的值
while( x != -1 ){//表示结束的值
InsertNextNode(&L,x);
scanf("%d",&x);//读入新的数据
}
return L;//返回表头指针
}
- 双链表可进可退
- 存储密度更低一点(需要存放指向前驱结点的指针)
typedef struct DNote{//定义双链表结点类型
ElemType daa;//数据域
struct DNode *prior,*next;//前驱和后驱指针
}DNote,*DLinkList;
bool InitDLinkList{DLinkList &L}{
L = (DNode *)malloc(sizeof(DNode));//分配一个头结点
if (L == NULL)//内存不足,分配失败
return false;
L->prior = NULL;//头结点的prior结点永远指向NULL
L->next = NULL;//头结点之后暂时还没有结点
return ture;
}
bool IsEmpty{DLinkList L}{
if (L->next == NULL)//内存不足,分配失败
return false;
else
return false;
}
bool InsertNextNode{DNode *p,DNode *s}{
if (p == NULL || s == NULL)//非法参数
return false;
s->next = p->next;
if (p->next != NULL)//如果p结点有后继结点
p->next->prior = s;
p-next = s;
s->prior = p;
return false;
}
bool DeleteNextNode(DNode *p){
if (p == NULL)
return false;
DNode *q = p->next;//指向p的后继结点q
if (p->next == NULL)
return false;
p->next = q->next;
if (q->next != NULL)//q结点不是最后一个结点
q->next->prior = p;
free(q);//释放结点空间
return true;
}
void DestoryList(DLinkList &L){
//循环释放各个数据结点
while(L->next != NULL)
DeleteNextNode(L);
free(L);//释放头结点
L = NULL;//头指针指向NULL
}
while (p->prior != NULL){
//对结点p做相应处理
p = p->prior;
}
bool InitList(LinkList &L){
L = (LNode *)malloc(sizeof(LNode));//分配一个结点
if (L == NULL)//内存不足,分配失败
return false;
L->next = L;//头结点next指向头结点
return true;
}
bool IsEmpty(LinkList L){
if (L->next == L)//单手抱紧自己
return true;
else return false;
}
bool IsTail(LinkList L, LNode *p){
if (p->next == L)
return true;
else return false;
}
bool InitList(DLinkList &L){
L = (LNode *)malloc(sizeof(LNode));//分配一个结点
if (L == NULL)//内存不足,分配失败
return false;
L->next = L;//头结点next指向头结点
L->prior = L;//头结点prior指向头结点
return true;
}
bool IsEmpty(DLinkList L){
if (L->next == L)//单手抱紧自己
return true;
else return false;
}
bool IsTail(DLinkList L, LNode *p){
if (p->next == L)
return true;
else return false;
}
bool InsertNextNode{DNode *p,DNode *s}{
if (p == NULL || s == NULL)//非法参数
return false;
s->next = p->next;
p->next->prior = s;
p-next = s;
s->prior = p;
return false;
}
bool DeleteNextNode(DNode *p){
if (p == NULL)
return false;
DNode *q = p->next;//指向p的后继结点q
p->next = q->next;
q->next->prior = p;
free(q);//释放结点空间
return true;
}
- 分配一整片连续的内存空间,各个结点集中安置
- 利用数组的方式实现链表的功能(结构数组)
#define MaxSize 10//静态链表的最大长度
struct Node{
ElemType data;//存储数据元素
int next;//下一个元素的数组下标
};
#define MaxSize 10//静态链表的最大长度
typedef struct {//静态链表结构类型的定义
ElemType data;//存储数据元素
int next;//下一个元素的数组下标
}SLinkList[MaxSize];
/* 等价以下代码
#define MaxSize 10//静态链表的最大长度
struct Node{//静态链表结构类型的定义
ElemType data;//存储数据元素
int next;//下一个元素的数组下标
};
//SLinkList 定义 “一个长度为MaxSize 的Node 型数组”
typedef struct Node SlinkList[MaxSize];
*/
#define MaxSize 10//静态链表的最大长度
typedef struct {//静态链表结构类型的定义
ElemType data;//存储数据元素
int next;//下一个元素的数组下标
}SLinkList[MaxSize];
/* 等价以下代码
#define MaxSize 10//静态链表的最大长度
struct Node{//静态链表结构类型的定义
ElemType data;//存储数据元素
int next;//下一个元素的数组下标
};
//SLinkList 定义 “一个长度为MaxSize 的Node 型数组”
typedef struct Node SlinkList[MaxSize];
*/