408数据结构-双链表、循环链表和静态链表 自学知识点整理

单链表结点中只有一个指向其后继的指针,使得单链表只能从前往后依次遍历。要访问某个节点的前驱(插入、删除操作时),只能从头开始遍历,访问前驱的时间复杂度为O(n)。为了克服单链表这个缺点,引入了 双链表 。双链表结点中有两个指针prior和next,分别指向其直接前驱和直接后继。

  • 双链表

定义一个双链表,代码风格延续上一篇单链表相关的文章:

typedef struct DNode {
	int data;
	DNode* prior, * next;//前驱指针与后续指针
}DNode, * DLinkList;

初始化

bool InitDLinkList(DLinkList& L) {//初始化双链表
	L = (DNode*)malloc(sizeof(DNode));
	if (L == NULL)return false;
	L->prior = NULL, L->next = NULL;//头结点的前驱永远指向NULL
	return true;
}

判空

bool IsEmpty(DLinkList& L) {//判断双链表是否为空
	return L->next == NULL ? true : false;
}

按值查找

DNode* GetElem(DLinkList& L,int e) {//按值查找数据,返回相应结点(同单链表)
	DNode* r = L;
	for (; r->next != NULL && r->data != e; r = r->next);
	return r;
}

按位查找

DNode* FindElem(DLinkList& L, int i) {//按位查找数据,返回相应结点(带头结点,同单链表)
	if (i < 1)return NULL;//判断i是否合法
	int j = 0;
	DNode* r = L;
	for (; r->next != NULL && j < i; r = r->next, ++j);//从表头开始遍历
	return r;
}

后插操作

bool InsertNextDNode(DNode* p, DNode* s) {//将s结点插到p结点之后
	if (p == NULL || s == NULL)return false;//排除非法参数
	s->next = p->next;//s的后继指向p的后继
	if (p->next != NULL)p->next->prior = s;//如果p结点有后继,则p的后继的前驱指向s
	s->prior = p;//s的前驱指向p
	p->next = s;//p的后继指向s,完成插入
	return true;
}

前插操作,与单链表实现逻辑不同:

bool InsertPriorDNode(DNode* p, DNode* s) {//将s结点插到p结点之前
	if (p == NULL || s == NULL)return false;
	s->prior = p->prior;
	p->prior->next = s;//如果p是第一位序结点,则p的前驱将会是头指针L,所以不存在NULL的情况
	s->next = p;
	p->prior = s;
	return true;
}

按位插入

bool InsertDNode(DLinkList& L,DNode* p, int i) {//将p插到第i个位置
	return InsertNextDNode(p, FindElem(L, i - 1));
	//直接调用按位查找函数和后插函数,找到第i-1位序的结点,在其后插入结点p
}

删除指定结点的后继结点:

bool DeleteNextDNode(DNode* p) {//删除p的后继结点
	if (p == NULL || p->next == NULL)return false;//判断p是否合法以及p是否有后继结点
	DNode* q = p->next;
	p->next = q->next;//将p的后继用q的后继替换
	if (q->next != NULL)q->next->prior = p;//若q有后继结点,则q的后继的前驱用p替换
	free(q);//释放q结点
	return true;
}

销毁双链表

void DestoryList(DLinkList& L) {//销毁双向链表
	while (L->next != NULL)DeleteNextDNode(L);
	free(L);
	L = NULL;
	return;
}

双链表的建立基本同单链表,也可采用头插法或尾插法,但是操作上需要注意指针的变化和单链表有所不同。完整代码可参阅我的Github:传送门


  • 循环链表

循环单链表

循环单链表和单链表的区别在于,表尾元素的next指针指向的不是NULL,而是改为指向头结点,从而让整个链表形成一个环。
定义循环单链表与定义单链表完全一致:

#define LNode LLNode
#define LinkList LLinkList
//上面两行没有任何意义
typedef struct LNode {//循环单链表
	int data;
	struct LNode* next;
}LNode, * LinkList;

初始化时需注意头结点的next指针指向自己:

bool InitLLinkList(LinkList& L) {//循环单链表初始化
	L = (LNode*)malloc(sizeof(LNode));
	if (L == NULL)return false;
	L->next = L;//初始状态头结点的next指针指向头结点自身
	return true;
}

判空

bool LEmpty(LinkList& L) {//循环单链表判空
	return L->next == L ? true : false;
}

断结点是否在表

bool LisTail(LinkList& L, LNode* p) {//判断p结点是否为循环单链表的表尾结点
	return p->next == L ? true : false;
}

循环单链表可以从任一结点开始顺序遍历整个链表,有时不设头指针而设尾指针,使得代码的执行效率更高。例如,若设的是尾指针,则在表头或表尾插入元素的时间复杂度均为O(1),而设头指针的话在表尾插入元素的时间复杂度就是O(n)。
其余基本操作同单链表基本相同,故不作赘述。

循环双链表

循环双链表中,prior和next指针分别构成一个环,因此在初始化操作时需要让这两个指针都指向头结点。
定义一个循环双链表的操作与定义一个双链表相同:

#define DNode DLNode
#define DLinkList DLLinkList
//上面两行没有任何作用
typedef struct DNode {
	int data;
	struct DNode* prior, * next;
}DNode, * DLinkList;

初始化

bool InitDLinkList(DLinkList& L) {//循环双链表初始化
	L = (DNode*)malloc(sizeof(DNode));
	if (L == NULL)return false;
	L->prior = L, L->next = L;//初始状态头结点的前驱与后继均为自身
	return true;
}

判空

bool DisEmpty(DLinkList& L) {//循环双链表判空
	return L->next == L ? true : false;
}

判尾

bool DisTail(DLinkList& L, DNode* p) {//判断p结点是否为循环双链表的表尾结点
	return p->next == L ? true : false;
}

对给定结点进行后插操作

bool InsertDNextNode(DNode* p, DNode* s) {//在循环双链表中p结点后插入s结点
	if (p == NULL || s == NULL)return false;
	s->next = p->next;//s的后继指向p的后继
	p->next->prior = s;//p的后继的前驱指向s,此时不用担心p是表尾结点了
	p->next = s;//p的后继指向s
	s->prior = p;//s的前驱指向p,完成插入
	return true;
}

删除指定结点的后续结点:

bool DeleteDNode(DNode* p) {//删除循环双链表结点p的下一个结点q
	DNode* q = p->next;
	q->next->prior = p;//此时也不用担心q是表尾结点
	p->next = q->next;
	free(q);
	return true;
}

其余基本操作与双链表大差不差,故不作赘述。


  • 静态链表

静态链表 是用数组来描述线性表的链式储存结构,结点也有数据域data和指针域next,但与单双链表中的指针不同的是,这里的指针是结点在数组中的相对地址(数组下标),又称 游标
定义一个静态链表:

#define MaxSize 10
typedef struct{//静态链表
	int data;
	int next=-2;//游标,用next存放下一个结点的数组下标
}SLinkList[MaxSize];
SLinkList a;//虽然效果上等价,但是这种写法可读性更强

等价于:

typedef struct SNode{
	int data;
	int next;
}SNode;
SNode a[MaxSize];

静态链表以next=-1作为其结束的标志(表尾元素),其基本操作也与动态链表的相同,只需要修改指针,不需要移动元素。总的来说,静态链表没有单链表使用起来方便,但在一些不支持指针的高级语言(如Basic)中仍有使用余地,在408初试中考察较少。
个人感觉静态链表和散列表(哈希)有点像,需要专门写一个函数来计算next确保不会重复,除此之外我也想不到什么办法来计算和更改指针了……
(当然估计是我太菜了

关于循环链表和静态链表的知识点基本上就这些,完整代码可看我的Github:传送门

以上。

  • 19
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值