《算法导论》第10章-基本数据结构 10.1 栈和队列;10.2 链表

10.1 栈和队列

一、栈(stack)(后进先出)

1、属性及操作

①top:指向最新插入的元素(起到的是下标作用)。
②empty:是否为空。

STACK-EMPTY
if S.top == 0
	return TRUE
else return FALSE

③push:向栈内压入元素x。

PUSH(S,x)
S.top = S.top + 1
S[S.top] = x

④pop:弹出最上面(S.top)位置上的元素。

if STACK-EMPTY(S)
	error "underflow"//栈下溢,即在试图对一个空栈执行弹出操作时
else S.top = S.top - 1
	return S[S.top+1]

2、图解

在这里插入图片描述
(a)通过PUSH(S,17)\PUSH(S,3)变成(b)
(b)通过POP(S)变成(c)

二、队列(queue)(先进先出)

1、属性及操作

①head:指向队头元素。
②tail:指向队尾元素。
③ENQUEUE(Q,x):元素x入队操作,进入tail位置。

ENQUEUE(Q,x)
Q[Q.tail] = x		//将x放在尾部上
if Q.tail ==Q.length	//如果tail已经在尾部了,那么将tail指向队列第1位(像是一个环绕),供下一个元素放入
	Q.tail = 1
else Q.tail = Q.tail + 1	//否则tail往后移动一位(供下一个元素放入)

④DEQUEUE(Q):head位置的元素出队操作

DEQUEUE(Q)
x = Q[Q.head]	//x作为头部(head所指位置)元素出队
if Q.head = Q.length	//同ENQUEUE的移动方式,也是类似一个环绕
	Q.head = 1
else Q.head = Q.head + 1
return x

2、图解

在这里插入图片描述
(a)通过ENQUEUE(Q,17)将17放置在Q[12]的位置上,然后tail变成1
再通过ENQUEUE(Q,3)、ENQUEUE(Q,5)将3、5加入队列,最后tail=3。
(b)通过DEQUEUE(Q)弹出队头(head = 7的位置)15,然后head=8,最终新的队头关键字为6。

10.2 链表

一、最简单的单向链表(single linked list)(书上没讲,但我觉得有必要提)

1、单向链表基本知识

在这里插入图片描述
简单来讲,就是单向链表的每一个结点中包含data(数据域)和next(指针域),每一个结点(除了tail)的数据域用于存储数据,指针域用于存储下一个结点的地址(图中箭头就相当于一个指针),通过这种“箭头”我们将各个结点连接起来,从而形成链表。

2、单向链表各个操作的代码(C语言,推荐用VS运行,其中包含辅助指针思想)

(1) Linklist.h

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include<stdbool.h>
#define _CRT_SECURE_NO_WARNINGS
#pragma once//防止头文件重复

#ifdef __cplusplus
extern "C" {
#endif
	//函数放在里面
	//定义节点数据类型
	struct LinkNode {
		int data;
		struct LinkNode* next;
	};

	//初始化链表
	struct LinkNode* Init_LinkList();
	//在值为oldval的后面添加一个新的数据newval
	void InsertByValue_LinkList(struct LinkNode* header, int oldval, int newval);
	//删除值为val的节点
	void RemoveByValue_LinkList(struct LinkNode* header, int delValue);
	//遍历
	void Foreach_LinkList(struct LinkNode* header);
	//销毁
	void Destroy_LinkList(struct LinkNode* header);
	//清空
	void Clear_LinkList(struct LinkNode* header);

#ifdef __cplusplus
}
#endif

*(2)Linklist.c(最重要,内含各种基本操作)

#define _CRT_SECURE_NO_WARNINGS
#include "Linklist.h"
#include<stdio.h>
//初始化链表
struct LinkNode* Init_LinkList()
{
	//struct LinkNode* newnode;
	//拿到头结点,就相当于拿到整个链表
	struct LinkNode* header = malloc(sizeof(struct LinkNode));
	header->data = -1;
	header->next = NULL;

	int val = -1;
	struct LinkNode* pRear;
	pRear = header;//现在还指向头结点的首地址
	while (true) {
		printf("请输入一个值!\n");
		scanf("%d", &val);
		if (val == -1) {
			break;
		}
		//创建一个新的节点
		//newnode = (struct LinkNode*)malloc(sizeof(struct LinkNode));
		struct LinkNode* newnode = malloc(sizeof(struct LinkNode));
		newnode->data = val;
		newnode->next = NULL;
		//新节点插入到链表中
		pRear->next = newnode;
		//更新尾部指针指向
		pRear = newnode;
	}
	//pRear->next = NULL;
	return header;//返回头结点相当于把链表返回
};


//在值为oldval的位置插入一个新的数据newval
void InsertByValue_LinkList(struct LinkNode* header, int oldval, int newval)
{
	if (header == NULL) {
		return;
	}
	//法一
	//struct LinkNode* pPrev = header;
	//while (pPrev != NULL && pPrev->next->data != oldval) {
	//	pPrev = pPrev->next;
	//}
	创建新节点
	//struct LinkNode* newnode = malloc(sizeof(struct LinkNode));
	//newnode->data = newval;
	//newnode->next = NULL;
	新节点插入到链表中
	//newnode->next = pPrev->next;
	//pPrev->next = newnode;

	//定义两个辅助指针变量(法二)
	struct LinkNode* pCurrent = header->next;
	struct LinkNode* pPrev = header;
	struct LinkNode* pCurrent = pPrev->next;

	while (pCurrent != NULL) {
		if (pCurrent->data == oldval) {
			break;
		}
		pPrev = pCurrent;
		pCurrent = pCurrent->next;
	}
	//如果pCurrent为NULL,说明链表中不存在值为oldval的节点
	if (pCurrent == NULL) {
		return;
	}
	//创建新节点
	struct LinkNode* newnode = malloc(sizeof(struct LinkNode));
	newnode->data = newval;
	newnode->next = NULL;
	//新节点插入到链表中
	newnode->next = pCurrent;
	pPrev->next = newnode;

}


//删除值为val的节点
void RemoveByValue_LinkList(struct LinkNode* header, int delValue)
{
	//添加辅助指针
	struct LinkNode* pPrev = header;
	struct LinkNode* pCurrent = pPrev->next;
	if (header == NULL) {
		return;
	}

	while (pCurrent != NULL) {
		if (pCurrent->data == delValue) {
			break;
		}
		pPrev = pCurrent;
		pCurrent = pCurrent->next;
	}
	if (pCurrent == NULL) {
		return;
	}
	//重新建立删除节点和待删除节点前驱和后继节点关系
	pPrev->next = pCurrent->next;//让pprev的next指针指向删除节点的后一个
	//释放删除节点内存
	free(pCurrent);
	pCurrent = NULL;
}


//遍历
void Foreach_LinkList(struct LinkNode* header)
{
	if (header == NULL)
	{
		return;
	}
	//添加辅助指针变量
	struct LinkNode* pCurrent;
	pCurrent = header->next;//header不需要遍历
	while (pCurrent != NULL) {
		printf("%d ", pCurrent->data);
		pCurrent = pCurrent->next;
	}

}


//销毁
void Destroy_LinkList(struct LinkNode* header)
{
	if (header == NULL)
	{
		return;
	}
	//建立辅助指针

	struct LinkNode* pCurrent = header;

	while (pCurrent != NULL) {
		//先保存下一个节点的节点地址
		struct LinkNode* pNext = pCurrent->next;
		//释放当前节点内存
		printf("%d节点被销毁。\n", pCurrent->data);
		free(pCurrent);
		//指针向后移动(重新生成pCurrent)
		pCurrent = pNext;
	}

}


//清空(只有头结点,然后next指向空)
void Clear_LinkList(struct LinkNode* header)
{
	if (header == NULL)
	{
		return;
	}
	//定义辅助指针变量
	struct LinkNode* pCurrent = header->next;
	while (pCurrent != NULL) {
		//先保存下一个节点的地址
		struct LinkNode* pNext = pCurrent->next;

		//释放当前节点内存
		free(pCurrent);

		//pCurrent指向下一个节点
		pCurrent = pNext;
	}
	header->next = NULL;
}


(3)TestLinklist.c(用于测试)

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "Linklist.h"
void test() {
	//初始化链表 100~600
	struct LinkNode* header = Init_LinkList();
	//打印链表
	Foreach_LinkList(header);
	//插入数据
	InsertByValue_LinkList(header, 300, 666);
	打印链表
	printf("\n------\n");
	Foreach_LinkList(header);
	//删除节点
	//printf("\n------\n");
	//RemoveByValue_LinkList(header,300);
	//Foreach_LinkList(header);
	清空链表
	//printf("\n------\n");
	//Clear_LinkList(header);
	//Foreach_LinkList(header);
	//销毁链表
	//Destroy_LinkList(header);
}

int main()
{
	test();

	system("pause");
	return EXIT_SUCCESS;
}

二、更加复杂的链表

1、双向链表(double linked list)

(1)基本描述:

双向链表中每一个元素都是一个对象(结点),每一个对象有一个关键字key(data)和两个指针(域):next和prev,分别指向后继元素和前驱元素。如果x.prev = NIL,则元素x没有前驱,因此它是链表的(head);如果x.next = NIL,同理,它是链表的尾(tail)。
属性L.head指向链表的头,如果L.head=NIL,那么链表为空。

(2)图解:

在这里插入图片描述
(a):表示双向链表{1,4,9,16},表头的prev和表尾的next都等于NIL,用斜杠表示,执行了LIST-INSERT(L,X)(x.key=25)变成了(b)中的新链表,且新链表以25为表头。
(b):调用LIST-DELETE(L,X)(x.key=4)后变成了(c)中新链表。

2、其他链表

1、未排序链表:元素以任何顺序出现;已排序链表:按照关键字的线性顺序排序。
2、循环链表:表头的prev指针指向表尾元素,表尾元素的next指针指向表头元素(前面的图加两个箭头)。

三、链表的操作(更加普适化的方法,推荐结合上面的C语言代码)

注意:伪代码里的x指的既是指针,也是结点内部的关键字内容,真实代码肯定要更复杂。

1、链表的搜索

LIST-SEARCH(L,k)
x = L.head
while x != NIL and x.key != k	//遍历查找,如果x与k不相同则继续往后面的元素查找
	x = x.next
return x

2、链表的插入(将x连接到链表前端,上面C语言代码中可以将x连接到链表中间)

LIST-INSERT(L,x)
x.next = L.head
if L.head != NIL	
	L.head.prev = x
	L.head = x
	x.prev = NIL

3、链表的删除

LIST-DELETE(L,x)
if x.prev != NIL
	x.prev.next = x.next
else L.head = x.next
if x.head != NIL
	x.next.prev = x.prev

4、哨兵(sentinel)(慎用)

(1)基本描述:

①哨兵是一个哑对象,作用是简化边界条件的处理。引入一个对象L.nil,将原来在代码中出现的NIL都换成对L.nil的引用。这样使双向链表成为有哨兵的双向链表。
②哨兵L.nil位于表头和表尾之间,L.nil.next指向表头,L.nil.prev指向表尾;同时表头的prev和表尾的next指向L.nil。
③因为L.nil.next指向表头,因此我们不再需要L.head。

(2)图解

在这里插入图片描述
(a):空链表。
(b):L.nil就如上面的基本描述所说,连接表头和表尾。
(c)~(d):插入删除的操作挺直观的,这里不再赘述。

(3)有哨兵后需要改变的方法
LIST-SEARCH'(L,k)
x = L.nil.next
while x != L.nil and x.key != k	//遍历查找,如果x与k不相同则继续往后面的元素查找
	x = x.next
return x
LIST-INSERT'(L,x)
x.next = L.nil.next
L.nil.next.prev = x	
L.head.prev = x
L.nil.next = x
x.prev = L.nil
LIST-DELETE'(L,x)
x.prev.next = x.next
x.next.prev = x.prev
(4)慎用哨兵

如果是很短的链表,用哨兵会占用额外的存储空间造成存储浪费,我们只需在真正简化代码时才使用哨兵。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

KeepCoding♪Toby♪

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值