1、什么是链表
链表是一种基础的数据结构,是一种线性表,存储有序的元素集合,链表的每个元素由一个存储元素本身的节点和下一个元素的引用组成。
现实中的链表:火车、链条、寻宝游戏、许多人手牵着手
常见的链表结构:
1.单向链表
2.双向链表
3.循环链表
4.有序链表
优势:
1.相对于数组,添加或移除元素不需要移动其他元素,在进行添加或移除元素时的效率高。
2.内存利用率高,只有在需要的时候才会创建内存空间。
劣势:
查找链表的元素效率低,必须从链表的首节点或尾节点开始向后遍历查找
2、单向链表
概念:单向链表的链接方向是单向的,对单向链表的访问要通过顺序读取从头部开始,即单向链表能从头节点正向遍历访问下一个节点,直到单向链表的尾节点,单向链表的尾节点的指向永远为null,单向链表也不能逆向遍历前面的单向链表节点。
劣势:
查找链表的元素效率低,必须从链表的第一个节点开始向后遍历查找。
代码实现:
1. 带头节点的单链表
list.h
#pragma once
//带头节点的单链表
/*
* 单链表的特点:
* 1.头插,头删的时间复杂度是O(1)
* 2.尾插,尾删的时间复杂度是O(n)
* 3.最后一个节点的后继为NULL
*/
//单链表
typedef int ElemType;
typedef struct LNode//单链表 节点类型
{
ElemType data;//数据域
struct LNode* next;//指向下一个节点,后继指针
}LNode,*LinkList;
//LinkList==LNode*,但是语义不同
//LinkList指代整个单链表,特指头节点的地址
//LNode*指节点的地址,可以是任意节点的地址
//初始化
void InitList(LinkList pl);
//销毁整个内存
void Destroy(LinkList pl);
//清空数据
void Clear(LinkList pl);
//判空
bool IsEmpty(LinkList pl);
//获取长度
int GetLength(LinkList pl);
//获取i下标的值
bool GetElem(LinkList pl, int i, ElemType* rtval);
//返回key的前驱地址,如果不存在返回NULL
LNode* GETPrio(LinkList pl, int val);
//返回key的后继地址,如果不存在返回NULL
LNode* GETNext(LinkList pl, int val);
//获取i下标的前驱值
bool GetPrio(LinkList pl, int i, ElemType* rtval);
//获取i下标的后继值
bool GetNext(LinkList pl, int i, ElemType* rtval);
//查找val的位值,找到返回节点地址,失败返回NULL
LNode* Seach(LinkList pl, int val);
//在i下标插入数据val
bool Insert(LinkList pl, int i, ElemType val);
//头插
bool Insert_head(LinkList pl, ElemType val);
//尾插
bool Insert_tail(LinkList pl, ElemType val);
//删除pos位置的值
bool DelPos(LinkList plist, int pos);
//删除第一个val的值
bool Delete(LinkList pl, ElemType val);
//输出
void Show(LinkList pl);
list.cpp
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include "list.h"
/*
* 遍历链表所有节点总结:
* 1.需要前驱(需要修改链表结构) for(LNode *p=pl;p->next!=NULL;p=p->next)...
* 2.不需要前驱,只遍历数据节点 for(LNode *p=pl->next;pl!=NULL;p=p->next)...
*/
//初始化
void InitList(LinkList pl)
{
assert(pl != NULL);
if (pl == NULL)
{
return;
}
//pl->data; //头节点的数据域不使用
pl->next = NULL;
}
//销毁整个内存
void Destroy(LinkList pl)
{
LNode* p;
//总是删除第一个节点(食堂阿姨打饭)
while (pl->next != NULL)//还有第一个节点
{
p = pl->next;//保存第一个节点
pl->next = p->next;//剔除第一个节点
free(p);//删除第一个节点
}
//LNode* p = pl->next;
//LNode* q;//p的后继
//pl->next = NULL;
//while (p != NULL)
//{
// q = p->next;
// free(p);
// p = q;
//}
}
//清空数据(原来的数据节点还要吗?,当前的设计是不要)
void Clear(LinkList pl)
{
Destroy(pl);
}
//判空
bool IsEmpty(LinkList pl)
{
assert(pl != NULL);
if (pl == NULL)
return false;
return pl->next == NULL;//只有头节点
}
//获取长度(数据个数,不包括头节点)
int GetLength(LinkList pl)
{
assert(pl != NULL);
if (pl == NULL)
return 0;
int count = 0;
//遍历所有的数据节点
for (LNode* p = pl->next; p != NULL; p = p->next)
{
count++;
}
return count;
}
//获取i下标的值,设第一个数据节点的下标为0
bool GetElem(LinkList pl, int i, ElemType* rtval)//O(n)
{
if (i < 0 || i >= GetLength(pl))
return false;
int j = 0;
LNode* p;
for (p = pl->next;j < i; j++, p = p->next)
;//空语句
*rtval = p->data;
return true;
}
//返回key的前驱地址,如果不存在返回NULL
LNode* GETPrio(LinkList pl, int key)
{
for (LNode* p = pl; p->next != NULL; p = p->next)
{
if (p->next->data == key)
{
return p;
}
}
return NULL;
}
//返回key的后继地址,如果不存在返回NULL
LNode* GETNext(LinkList pl, int key)
{
assert(pl != NULL);
if (pl == NULL)
return NULL;
LNode* p = Seach(pl, key);
if (p == NULL)
return NULL;
return p->next;
}
//获取i下标的前驱值
bool GetPrio(LinkList pl, int i, ElemType* rtval)//O(n)
{
if (i <= 0 || i >= GetLength(pl))
return false;
LNode* p;
int j = 0;
for (p = pl; j < i; ++j, p = p->next)//找到i的前驱节点
;
*rtval = p->data;
return true;
}
//获取i下标的后继值
bool GetNext(LinkList pl, int i, ElemType* rtval)//O(n)
{
if (i < 0 || i >= GetLength(pl)-1)
return false;
int j = 0;
LNode* p;
for (p = pl->next; j < i; ++j, p = p->next)//找到i下标的数据节点
;
*rtval = p->next->data;
return true;
}
//查找val的位值,找到返回节点地址,失败返回NULL
LNode* Seach(LinkList pl, int val)//LinkList==LNode*,但是语义不同
{
assert(pl != NULL);
if (pl == NULL)
return NULL;
for (LNode* p = pl->next; p != NULL; p = p->next)
{
if (p->data == val)
return p;
}
return NULL;
}
//在i下标插入数据val
bool Insert(LinkList pl, int i, ElemType val)
{
if (i<0 || i>GetLength(pl))
return false;
//创建新节点
LNode* p = (LNode*)malloc(sizeof(LNode));
assert(p != NULL);
if (p == NULL)
return false;
p->data = val;
//找到合适的位置
LNode* q;
int j = 0;
for (q = pl; j < i; ++j, q = q->next)
{
;
}
//插入,p插入在q的后面
p->next = q->next;
q->next = p;
return true;
}
//考试重点
//头插
bool Insert_head(LinkList pl, ElemType val)
{
//LNode newnode = { val };//局部变量:生命周期(进入函数创建,函数结束销毁)
//newnode.next = pl->next;
//pl->next = &newnode;
LNode* p = (LNode*)malloc(sizeof(LNode));//动态申请一个节点
assert(p != NULL);
if (p == NULL)
return false;
//将数据val放入到新节点
p->data = val;
//插入新节点
p->next = pl->next;
pl->next = p;
return true;
}
//考试重点
//尾插
bool Insert_tail(LinkList pl, ElemType val)
{
//创建新节点
LNode* p = (LNode*)malloc(sizeof(LNode));
assert(p != NULL);
if (p == NULL)
return false;
//将数据val放入到新节点
p->data = val;
//找尾巴
LNode* q;
for (q = pl; q->next != NULL; q = q->next)
{
;
}
//新节点插在尾巴的后面
p->next = q->next;//p->next=NULL
q->next = p;
return true;
}
//删除pos位置的值
bool DelPos(LinkList pl, int pos)
{
assert(pl != NULL);
if (pl == NULL)
return false;
if (pos < 0 || pos >= GetLength(pl))
{
return false;
}
LNode* p;
int i;
for (p = pl, i = 0; i < pos; i++, p = p->next)
{
;
}
//删除p后面的节点
LNode* q = p->next;
p->next = q->next;
free(q);
}
//考试重点
//删除第一个val的值
bool Delete(LinkList pl, ElemType val)
{
/*LNode* p = Seach(pl, val);
if (p == NULL)
return false;*///错误
//1.找到val的前驱节点
LNode* p;
for (p = pl; p->next != NULL; p = p->next)
{
if (p->next->data == val)//找到了
break;
}
if (p->next == NULL)//没找到
return false;
//2.把val节点从链表中剔除
LNode* q = p->next;//q就是将要删除的节点
p->next = q->next;//剔除
//3.删除val节点
free(q);
return true;
}
//输出
void Show(LinkList pl)
{
//注意,头节点不能访问data
for (LNode* p = pl->next; pl != NULL; p = p->next)
{
printf("%d ", p->data);
}
printf("\n");
}
2. 不带头节点的单链表
nlist.h
#pragma once
//不带头节点的单链表(最后一个节点的后继为NULL)
//不带头节点的链表会出现二级指针,课本为了杜绝二级指针,增加了引用(C++的概念)
//需要修改头指针的指向,都需要传递头指针的地址(二级指针)
//力扣中的所有链表都是不带头节点,在做题时可以自己创建一个局部的头节点,最后返回头节点的next即可
typedef int ElemType;
typedef struct ListNode//不带头节点单链表节点类型
{
ElemType val;//数据域
struct ListNode* next;//指向下一个节点,后继指针
}ListNode, * NLinkList;
//初始化
void InitList(NLinkList *ppl);
//销毁整个内存
void Destroy(NLinkList *ppl);
//清空数据
void Clear(NLinkList *ppl);
//判空
bool IsEmpty(NLinkList pl);
//获取长度
int GetLength(NLinkList pl);
//获取i下标的值
bool GetElem(NLinkList pl, int i, ElemType* rtval);
//获取i下标的前驱值
bool GetPrio(NLinkList pl, int i, ElemType* rtval);
//获取i下标的后继值
bool GetNext(NLinkList pl, int i, ElemType* rtval);
//查找val的位值,找到返回节点地址,失败返回NULL
ListNode* Seach(NLinkList pl, int val);
//在i下标插入数据val
bool Insert(NLinkList pl, int i, ElemType val);
//头插
bool Insert_head(NLinkList *ppl, ElemType val);
//尾插
bool Insert_tail(NLinkList *ppl, ElemType val);
//删除第一个val的值
bool Delete(NLinkList *ppl, ElemType val);
//输出
void Show(NLinkList pl);
nlist.cpp
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include "nlist.h"
//初始化(头指针指向NULL)
void InitList(NLinkList *ppl)
{
assert(ppl != NULL);
if (ppl == NULL)
return;
*ppl = NULL;//没有头节点,头指针指向NULL
}
//销毁整个内存
void Destroy(NLinkList* ppl)
{
ListNode* p = *ppl;
ListNode* q;
while (p != NULL)//删除第一个数据节点
{
q = p;
p = p->next;
free(q);
}
*ppl = NULL;
}
//清空数据
void Clear(NLinkList* ppl)
{
Destroy(ppl);
}
//判空(没有数据节点)
bool IsEmpty(NLinkList pl)
{
return pl == NULL;
}
//获取长度
int GetLength(NLinkList pl)
{
int count = 0;
for (ListNode* p = pl; p != NULL; p = p->next)//遍历数据节点
{
count++;
}
return count;
}
//获取i下标的值
bool GetElem(NLinkList pl, int i, ElemType* rtval);
//获取i下标的前驱值
bool GetPrio(NLinkList pl, int i, ElemType* rtval);
//获取i下标的后继值
bool GetNext(NLinkList pl, int i, ElemType* rtval);
//查找val的位值,找到返回节点地址,失败返回NULL
ListNode* Seach(NLinkList pl, int val)
{
for (ListNode* p = pl; p != NULL; p = p->next)
{
if (p->val == val)
return p;
}
return NULL;
}
//在i下标插入数据val
bool Insert(NLinkList pl, int i, ElemType val);
//头插
bool Insert_head(NLinkList* ppl, ElemType val)
{
ListNode* p = (ListNode*)malloc(sizeof(ListNode));//创建新节点
assert(p != NULL);
p->val = val;
p->next = *ppl;
*ppl = p;
return true;
}
//尾插
bool Insert_tail(NLinkList* ppl, ElemType val)
{
ListNode* p = (ListNode*)malloc(sizeof(ListNode));//创建新节点
p->val = val;
p->next = NULL;
if (*ppl == NULL)//第一次插入
{
*ppl = p;
return true;
}
//找到尾节点
ListNode* q;
for (q = *ppl; q->next != NULL; q = q->next)
;
q->next = p;
return true;
}
//删除第一个val的值
bool Delete(NLinkList* ppl, ElemType val)
{
ListNode* p;
//删除的是第一个节点
if ((*ppl)->val == val)
{
p = *ppl;//标记第一个节点
*ppl = p->next;
free(p);
return true;
}
for (p = *ppl; p->next != NULL; p = p->next)
{
if (p->next->val == val)
{
ListNode* q = p->next;//标记需要删除的节点
p->next = q->next;
free(q);
return true;
}
}
return false;
}
//输出
void Show(NLinkList pl)
{
for (ListNode* p = pl; p != NULL; p = p->next)
{
printf("%d ", p->val);
}
printf("\n");
}
总结:
单链表的特点:
头插,头删 时间复杂度是O(1)
尾插,尾删 时间复杂度是O(n)
3、双向链表
概念:
双向链表和普通单向链表的区别在于,在单向链表中,一个节点只有链向下一个节点的链接;而在双向链表中,链接是双向的:一个链向下一个元素,另一个链向前一个元素。
在单向链表中,如果迭代时错过了要找的元素,就需要回到起点,重新开始迭代,而双向链表,可以从头到尾,或者从尾到头。我们也可以访问一个特定节点的下一个或前一个元素。
优势:
1.双向链表中提供了两种迭代的方法:从头到尾,或者从尾到头。
2.可以访问一个特定节点的下一个或前一个元素。
劣势:
1.每次添加或删除节点操作更复杂,需要操作的引用较多(4个)
2.内存空间占用比单向链表大
代码实现
dlist.h
#pragma once
//双向链表,带头节点,循环(头的前驱为尾节点,尾的后继尾为头节点)
//考多级指针判断
typedef int ElemType;
typedef struct DuLNode
{
ElemType data;
struct DuLNode* prior;//前驱指针
struct DuLNode* next;//后继指针
}DuLNode,* DuLinkList;
//初始化
void InitList(DuLinkList pl);
//销毁整个内存
void Destroy(DuLinkList pl);
//清空数据
void Clear(DuLinkList pl);
//判空
bool IsEmpty(DuLinkList pl);
//获取长度
int GetLength(DuLinkList pl);
//获取i下标的值
bool GetElem(DuLinkList pl, int i, ElemType* rtval);
//获取i下标的前驱值
bool GetPrio(DuLinkList pl, int i, ElemType* rtval);
//获取i下标的后继值
bool GetNext(DuLinkList pl, int i, ElemType* rtval);
//返回key的前驱地址,如果不存在返回NULL
DuLNode* GetPrio(DuLinkList pl, int key);
//返回key的后继地址,如果不存在返回NULL
DuLNode* GetNext(DuLinkList pl, int key);
//查找val的位值,找到返回节点地址,失败返回NULL
DuLNode* Seach(DuLinkList pl, int val);
//在i下标插入数据val
bool Insert(DuLinkList pl, int i, ElemType val);
//头插
bool Insert_head(DuLinkList pl, ElemType val);
//尾插
bool Insert_tail(DuLinkList pl, ElemType val);
//删除pos位置的值
bool DelPos(DuLinkList pl, int pos);
//删除第一个val的值
bool Delete(DuLinkList pl, ElemType val);
//输出
void Show(DuLinkList pl);
dlist.cpp
#include <stdio.h>
#include <stdlib.h>
#include "dlist.h"
#include <assert.h>
//初始化
void InitList(DuLinkList pl)
{
assert(pl != NULL);
if (pl == NULL)
return;
//plist->data 不使用
pl->next = pl;//环
pl->prior = pl;//环
}
//销毁整个内存
void Destroy(DuLinkList pl)//O(n)
{
assert(pl != NULL);
if (pl == NULL)
return;
//总是删除第一个数据节点
DuLNode* p;
while (pl->next != pl)
{
p = pl->next;
pl->next = p->next;//没有写前驱指针是对的
//pl->next->prior = pl;
free(p);
}
pl->prior = pl;
}
//清空数据
void Clear(DuLinkList pl)//O(n)
{
Destroy(pl);
}
//判空
bool IsEmpty(DuLinkList pl)//O(1)
{
return pl->next == pl;//没用数据节点
//return pl->next == NULL;
}
//获取长度
int GetLength(DuLinkList pl)//O(n)
{
int count = 0;
for (DuLNode* p = pl->next; p != pl; p = p->next)//遍历所有的数据节点
{
count++;
}
return count;
}
//获取i下标的值
bool GetElem(DuLinkList pl, int i, ElemType* rtval)//O(n)
{
if (i < 0 || i >= GetLength(pl))
return false;
DuLNode* p = pl->next;
for (int j = 0; j < i; j++, p = p->next)//找到i下标的节点
;
*rtval = p->data;
return true;
}
//获取i下标的前驱值
bool GetPrio(DuLinkList pl, int i, ElemType* rtval)//O(n)
{
if (i <= 0 || i >= GetLength(pl))
return false;
DuLNode* p = pl->next;
for (int j = 0; j < i; j++)//找到i下标的节点
p = p->next;
*rtval = p->prior->data;
return true;
}
//获取i下标的后继值
bool GetNext(DuLinkList pl, int i, ElemType* rtval)//O(n)
{
if (i < 0 || i + 1 >= GetLength(pl))
return false;
DuLNode* p = pl->next;
for (int j = 0; j < i; j++)//找i下标的节点
p = p->next;
*rtval = p->next->data;
return true;
}
//返回key的前驱地址,如果不存在返回NULL
DuLNode* GetPrio(DuLinkList pl, int key)
{
DuLNode* p = Seach(pl, key);
return p == NULL ? NULL : p->prior;
}
//返回key的后继地址,如果不存在返回NULL
DuLNode* GetNext(DuLinkList pl, int key)
{
DuLNode* p = Seach(pl, key);
return p == NULL ? NULL : p->next;
}
//查找val的位值,找到返回节点地址,失败返回NULL
DuLNode* Seach(DuLinkList pl, int val)//O(n)
{
for (DuLNode* p = pl->next; p != NULL; p = p->next)
if (p->data == val)
return p;
return NULL;
}
static DuLNode* BuyNode(int val)//申请一个新节点
{
DuLNode* p = (DuLNode*)malloc(sizeof(DuLNode));
assert(p != NULL);
if (p == NULL)
return NULL;
p->data = val;
return p;
}
//在pos下标插入数据val
bool Insert(DuLinkList pl, int pos, ElemType val)//O(n),请注意,一般说链表的插入时间复杂度O(1),只是单纯的插入,不包含查找过程
{
assert(pl != NULL);
if (pl == NULL)
return false;
if (pos < 0 || pos > GetLength(pl))
return false;
//申请节点
DuLNode* p = (DuLNode*)malloc(sizeof(DuLNode));
assert(p != NULL);
p->data = val;
//找位置
DuLNode* q;
int i = 0;
for (q = pl; i < pos; i++, q = q->next)//找i下标的前驱
{
;
}
//插入数据
//把p插入在q的后面
p->next = q->next;
q->next = p;
p->prior = q;
if (p->next != NULL)
{
p->next->prior = p;
}
return true;
}
//考试重点
//头插
bool Insert_head(DuLinkList pl, ElemType val)
{
assert(pl != NULL);
if (pl == NULL)
return false;
//创建新节点,并赋值
DuLNode* p = (DuLNode*)malloc(sizeof(DuLNode));
assert(p != NULL);
p->data = val;
//把p插入在pl的后面
p->next = pl->next;//把右边的指针先连接上
//连结左边两个指针
pl->next = p;
p->prior = pl;
//右边还有一个指针,需要连接
if (p->next != NULL)
{
p->next->prior = p;
}
return true;
}
//尾插
bool Insert_tail(DuLinkList pl, ElemType val)//O(1),不包括查找过程
{
assert(pl != NULL);
if (pl == NULL)
return false;
//创建一个新节点p,并赋值
DuLNode* p = (DuLNode*)malloc(sizeof(DuLNode));
assert(p != NULL);
p->data = val;
//找尾巴
DuLNode* q;
for (q = pl; q->next != NULL; q = q->next)
{
;
}
//插入数据
//将p插入到q的后面
p->next = q->next;
p->prior = q;
q->next = p;
return true;
}
//删除pos位置的值
bool DelPos(DuLinkList pl, int pos)
{
assert(pl != NULL);
if (pl == NULL)
return false;
if (pos < 0 || pos >= GetLength(pl))
{
return false;
}
//找位置
DuLNode* p;
int i;
for (p = pl, i = 0; i < pos; i++, p = p->next)
{
;
}
DuLNode* q = p->next;//q是要删除的点,注意,q可能是最后一个点
//删除
p->next = q->next;
if (q->next != NULL)
{
q->next->prior = q->prior;//OK
//q->next->prior = p;//OK q->prior就是p
}
//释放
free(q);
return true;
}
//考试重点
//删除第一个val的值
bool Delete(DuLinkList pl, ElemType val)
{
assert(pl != NULL);
if (pl == NULL)
return false;
DuLNode* p = Seach(pl, val);
if (p == NULL)
return false;
//把p从链表中剔除
p->prior->next = p->next;
if (p->next != NULL)
{
p->next->prior = p->prior;
}
free(p);
return true;
}
//输出
void Show(DuLinkList pl)
{
for (DuLNode* p = pl->next; pl != pl; p = p->next)
{
printf("%d ", p->data);
}
printf("\n");
}
总结:
双向链表其实和单链表一样,就是把两条方向的单链表都处理好就行
双向链表的考点就是多级指针,一定要注意判断
4、循环链表
概念:
循环链表可以像链表一样只有单向引用,也可以像双向链表一样有双向引用。循环链表和链表之间唯一的区别在于,最后一个元素指向下一个元素的指针,不是引用undefined,而是指向第一个元素。
优势:
遍历的时候可以从任意的节点开始,增加了遍历的灵活性
代码实现:
clist.h
#pragma once
//带头节点的循环链表,尾节点的后继为头节点
typedef int ElemType;
typedef struct CNode
{
ElemType data;//数据
struct CNode* next;//后继指针
}CNode,*CList;
//初始化
void InitList(CList pl);
//销毁整个内存
void Destroy(CList pl);
//清空数据
void Clear(CList pl);
//判空
bool IsEmpty(CList pl);
//获取长度
int GetLength(CList pl);
//获取i下标的值
bool GetElem(CList pl, int i, ElemType* rtval);
//获取i下标的前驱值
bool GetPrio(CList pl, int i, ElemType* rtval);
//获取i下标的后继值
bool GetNext(CList pl, int i, ElemType* rtval);
//返回key的前驱地址,如果不存在返回NULL
CNode* GetPrio(CList pl, int key);
//返回key的后继地址,如果不存在返回NULL
CNode* GetNext(CList pl, int key);
//查找val的位值,找到返回节点地址,失败返回NULL
CNode* Seach(CList pl, int val);
//在i下标插入数据val
bool Insert(CList pl, int i, ElemType val);
//头插
bool Insert_head(CList pl, ElemType val);
//尾插
bool Insert_tail(CList pl, ElemType val);
//删除pos位置的值
bool DelPos(CList plist, int pos);
//删除第一个val的值
bool Delete(CList pl, ElemType val);
//输出
void Show(CList pl);
clist.cpp
#include "clist.h"
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
//初始化
void InitList(CList pl)
{
assert(pl != NULL);
if (pl == NULL)
return;
//头节点的数据域不使用
pl->next = pl;//pl->next=NULL;//环
}
//销毁整个内存
void Destroy(CList pl)
{
//总是删除第一个数据节点
CNode* p;
while (pl->next != pl)
{
p = pl->next;//标记第一个节点
pl->next = p->next;//剔除第一个节点
free(p);//释放
}
}
//清空数据
void Clear(CList pl)
{
Destroy(pl);
}
//判空
bool IsEmpty(CList pl)
{
return pl->next == pl;
}
//获取长度
int GetLength(CList pl)
{
assert(pl != NULL);
if (pl == NULL)
return 0;
int count = 0;
for (CNode* p = pl->next; p != pl; p = p->next)
count++;
return count;
}
//获取i下标的值
bool GetElem(CList pl, int i, ElemType* rtval)
{
if (i < 0 || i >= GetLength(pl))
return false;
CNode* p;
int j;
for (j = 0, p = pl->next; j < i; p = p->next,j++)
;
*rtval = p->data;
return true;
}
//获取i下标的前驱值
bool GetPrio(CList pl, int i, ElemType* rtval)
{
if (i <= 0 || i >= GetLength(pl))
return false;
CNode* p;
int j;
for (j = 0, p = pl; j < i; p = p->next, j++)
;
*rtval = p->data;
return true;
}
//获取i下标的后继值
bool GetNext(CList pl, int i, ElemType* rtval)
{
if (i < 0 || i >= GetLength(pl) - 1)
return false;
CNode* p;
int j;
for (j = 0, p = pl->next; j < i; j++, p = p->next)
;
*rtval = p->next->data;
return true;
}
//返回key的前驱地址,如果不存在返回NULL
CNode* GetPrio(CList pl, int key)
{
CNode* p;
for (p = pl; p->next != pl; p = p->next)
{
if (p->next->data == key)
{
return p;
}
}
return NULL;
}
//返回key的后继地址,如果不存在返回NULL
CNode* GetNext(CList pl, int key)
{
CNode* p = Seach(pl, key);
if (p == NULL)
{
return NULL;
}
return p->next;
}
//查找val的位值,找到返回节点地址,失败返回NULL
CNode* Seach(CList pl, int val)
{
assert(pl != NULL);
if (pl == NULL)
return NULL;
for (CNode* p = pl->next; p != pl; p = p->next)
{
if (p->data == val)
{
return p;
}
}
return NULL;
}
//在i下标插入数据val
bool Insert(CList pl, int i, ElemType val)
{
if (i < 0 || i>GetLength(pl))
return false;
CNode* p = (CNode*)malloc(sizeof(CNode));
assert(p != NULL);
if (p == NULL)
return false;
p->data = val;
//找位置
CNode* q;
int j;
for (j = 0, q = pl; j < i; j++, q = q->next)
{
;
}
//插入
p->next = q->next;
q->next = p;
return true;
}
//头插
bool Insert_head(CList pl, ElemType val)//O(1)
{
//1.创建新节点
CNode* p = (CNode*)malloc(sizeof(CNode));
assert(p != NULL);
if (p == NULL)
return false;
//2.把数据存放到新节点
p->data = val;
//3.把新节点插入在头节点的后面
p->next = pl->next;
pl->next = p;
return true;
}
//尾插
bool Insert_tail(CList pl, ElemType val)//O(n)
{
//1.创建新节点
CNode* p = (CNode*)malloc(sizeof(CNode));
assert(p != NULL);
if (p == NULL)
return false;
//2.把值存放到新节点
p->data = val;
//3.找到尾节点
CNode* q;
for (q = pl; q->next != pl; q = q->next)
{
;
}
//4.把新节点插入到尾节点的后面
p->next = q->next;
q->next = p;
return true;
}
//删除pos位置的值
bool DelPos(CList plist, int pos)
{
if (pos < 0 || pos >= GetLength(pl))
{
return false;
}
CNode* p;
int i;
for (p = pl, i = 0; i < pos; i++, p = p->next)
{
;
}
CNode* q = p->next;//q是要删除的点
p->next = q->next;
free(q);
return true;
}
//删除第一个val的值
bool Delete(CList pl, ElemType val)
{
CNode* p;
for (p = pl; p->next != pl; p = p->next)
{
if (p->next->data == val)//找到了
{
CNode* q = p->next;//记录删除的节点
p->next = q->next;//删除
free(q);
return true;
}
}
return false;
}
//输出
void Show(CList pl)
{
//从第一个数据节点开始遍历
for (CNode* p = pl->next; p != pl; p = p->next)
printf("%d ", p->data);
printf("\n");
}
总结:
循环链表其实和单链表是一样的操作,只是在处理的时候处理好尾节点即可,切记,遍历循环链表中不可出现NULL,若遍历的时候出现NULL就错了。
5、静态链表
概念:
线性存储结构的一种,兼顾顺序表和链表的优点,是顺序表和链表的升级;静态链表的数据全部存储在数组中(顺序表),但存储的位置是随机的,数据之间的一对一关系是通过一个整形变量(称为“游标”,类似指针的功能)维持。用数组的方式实现链表,分配一整片连续的内存空间,各个结点集中安置。
每个数据元素4B,每个游标4B(每个结点共8B),设起始地址尾addr
e1的存放地址为:addr=8*2
优点:
在插入和删除操作时,只需要修改游标,不需要移动元素,从而改进了在顺序存储结构中的插入和删除操作需要移动大量元素的缺点。
缺点:
没有解决连续存储分配(数组)带来的表长难以确定的问题。失去了顺序存储结构随机存取的特性。
静态链表的初始化
把a[0]的next设为-1。
为了防止脏数据,可以把其他节点的next设为一个特殊值来表示空闲结点,如-2。
代码实现:
slist.h
#pragma once
#include "../栈和队列/minstack.h"
//静态链表
typedef struct SNode
{
int data;//数据
int next;//后继指针(下标)
}SNode,SLinkList[MAXSIZE];
//初始化
void InitList(SNode* ps);
//头插
bool Insert_head(SNode* ps, int val);
//尾插
bool Insert_tail(SNode* ps, int val);
//判空
bool IsEmpty(SNode* ps);
//获取数据节点的个数
int GetLength(SNode* ps);
//在ps中查找第一个key值,找到返回节点下标,没有找到返回-1
int Search(SNode* ps, int key);
int GetPrio(SNode* ps, int ket);
//删除第一个val的值
bool DelVal(SNode* ps, int val);
//输出
void Show(SNode* ps);
//清空数据
void Clear(SNode* ps);
//销毁整个内存
void Destroy(SNode* ps);
slist.cpp
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include "slist.h"
//初始化
void InitList(SNode* ps)
{
assert(ps != NULL);
if (ps == NULL)
return;
//处理有效链表
ps[0].next = 0;
//处理空闲链表
for (int i = 1; i < MAXSIZE; i++)
{
ps[i].next = i + 1;
}
ps[MAXSIZE - 1].next = 1;//1是空闲表头
}
static bool IsFull(SNode* ps)
{
return ps[1].next == 1;
}
//头插
bool Insert_head(SNode* ps, int val)
{
if (IsFull(ps))
{
return false;
}
//获取一个空闲节点
int p = ps[1].next;
//将空闲节点从空闲链表中剔除
ps[1].next = ps[p].next;
//放入数据
ps[p].data = val;
//将空闲节点插入到有效链表中
ps[p].next = ps[0].next;
ps[0].next = p;
return true;
}
//尾插
bool Insert_tail(SNode* ps, int val)
{
if (IsFull(ps))
{
return false;
}
//获取一个空闲节点
int p = ps[1].next;
//将空闲节点从空闲链表中剔除
ps[1].next = ps[p].next;
//放入数据
ps[p].data = val;
//找尾巴
int q;
for (q = 0; ps[q].next != 0; q = ps[q].next)
{
;
}
//将节点插入到有效链表中,p插入到q的后面
ps[p].next = ps[q].next;
ps[q].next = p;
return true;
}
//判空
bool IsEmpty(SNode* ps)
{
return ps[0].next == 0;//有效数据链表没有数据节点
}
//获取数据节点的个数
int GetLength(SNode* ps)
{
assert(ps != NULL);
if (ps == NULL);
return -1;
int count = 0;
//遍历有效链表
for (int p = ps[0].next; p != 0; p = ps[p].next)
{
count++;
}
return count;
}
//在ps中查找第一个key值,找到返回节点下标,没有找到返回-1
int Search(SNode* ps, int key)
{
assert(ps != NULL);
if (ps == NULL)
return -1;
//遍历有效链表
for (int p = ps[0].next; p != 0; p = ps[p].next)
{
if (ps[p].data == key)
{
return p;
}
}
return -1;
}
int GetPrio(SNode* ps, int key)
{
for (int p = 0; ps[p].next != 0; p = ps[p].next)
{
int q = ps[p].next;//q为p的后继
if (ps[q].data == key)
{
return p;
}
}
return -1;
}
//删除第一个val的值
bool DelVal(SNode* ps, int val)
{
//获取val的前驱
int p = GetPrio(ps, val);
if (p < 0)
return false;
//将节点从有效数据链表中剔除
int q = ps[p].next;
ps[p].next = ps[q].next;
//将节点添加到空闲链表中
ps[q].next = ps[1].next;
ps[1].next = q;
return true;
}
//输出
void Show(SNode* ps)
{
assert(ps != NULL);
if (ps == NULL)
return;
for (int p = ps[0].next; p != 0; p = ps[p].next)
{
printf("%d ", ps[p].data);
}
printf("\n");
}
//清空数据
void Clear(SNode* ps)
{
assert(ps != NULL);
if (ps == NULL)
return;
InitList(ps);
}
//销毁整个内存
void Destroy(SNode* ps)
{
Clear(ps);
}
总结:
1.静态链表,利用顺序表模拟链表
2.静态链表包含两条链表,一条为有效数据链表,另一条为空闲节点链表
3.有效数据链表为带头结点的循环链表,且头节点在0号下标
4.空闲数据链表为带头结点的循环链表,且头节点在1号下标
5.静态链表的优点:和顺序表对比,插入删除不需要移动数据,O(1)
6.静态链表的优点:和链表对比,不需要频繁的创建和删除节点
7.静态链表的缺点:和顺序表对比,需要增加一个next
8.静态链表可以动态增长,满后扩容,将扩容的内存添加到空闲链表