《算法导论》第十章——基本数据结构(栈、队列、链表)

  虽然写这个博客主要目的是为了给我自己做一个思路记忆录,但是如果你恰好点了进来,那么先对你说一声欢迎。我并不是什么大触,只是一个菜菜的学生,如果您发现了什么错误或者您对于某些地方有更好的意见,非常欢迎您的斧正!

10.1栈和队列


  栈(stack)又名堆栈,遵循后进先出原则。向一个栈插入新元素又称作进栈、入栈或压栈,它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;从一个栈删除元素又称作出栈或退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。

栈这一块我看来大概就是要学习这几个函数吧:
①初始化一个空栈InitStack()   使top=0
②判断栈是否非空StackEmpty()   判断top=0?
③入栈Push()
  1、top+1
  2、把新的元素赋值给arr[top]
④出栈Pop() 1、top-1 2、返回arr[top+1]

  因为突然忘记了结构体这一块,我补一下,你要是学的比较好,可以跳过这里。摘抄自这篇文章(https://blog.csdn.net/cynophile/article/details/79062494)

《算法导论》第十章——基本数据结构(栈、队列、链表)


  声明了一个结构体 struct node,如果需要声明一个它的对象,则可以这样:struct node n1;

《算法导论》第十章——基本数据结构(栈、队列、链表)


  相当于把代码改名为Node了。以前需要这样声明”struct node n1;”,现在只需要”Node n1;”。

在结构体中声明结构体变量。

《算法导论》第十章——基本数据结构(栈、队列、链表)


以下摘自(http://bbs.21ic.com/icview-1635522-1-1.html)

《算法导论》第十章——基本数据结构(栈、队列、链表)

《算法导论》第十章——基本数据结构(栈、队列、链表)

接下来是代码部分(建议复制后自己运行一下):

栈.h

#pragma once
typedef int dataType;
#define NUMBER_COUNT 10		/*栈里最多可以放的元素个数*/

struct Stack
{
	int top;
	dataType arr[NUMBER_COUNT];
};

/*①初始化一个空栈InitStack()*/
void InitStack(Stack *stack);

/*②判断栈是否非空StackEmpty()*/
bool StackEmpty(Stack *stack);

/*③入栈Push()*/
void Push(Stack *stack,dataType data);

/*④出栈Pop()*/
dataType Pop(Stack *stack);

/*栈的测试函数*/
void Teststack();

栈.cpp

#include "栈.h"
#include <stdlib.h>
#include <time.h>
#include <iostream>
using namespace std;

/*①初始化一个空栈InitStack()*/
void InitStack(Stack *stack)
{
	(*stack).top = 0;/*top为0就表示空栈*/
}

/*②判断栈是否非空StackEmpty()*/
bool StackEmpty(Stack *stack)
{
	if (0 == (*stack).top)
		return true;
	else return false;
}

/*③入栈Push()*/
void Push(Stack *stack, dataType data)
{
	if ((*stack).top == NUMBER_COUNT-1)
		cout << "栈已满!" << endl;
	else
	{
		(*stack).top++;/*因为多了一位,顶部下标加一*/
		(*stack).arr[(*stack).top] = data;
	}
}

/*④出栈Pop()*/
dataType Pop(Stack *stack)
{
	if (StackEmpty(stack))
	{
		cout << "栈是空的!" << endl;
		return 0;
	}
	else
	{
		(*stack).top--;
		return (*stack).arr[(*stack).top + 1];
	}
}

/*栈的测试函数*/
void Teststack()
{
	struct Stack stack;
	InitStack(&stack);/*初始化stack*/
	srand((unsigned)time(NULL));/*建立随机种子*/
	cout << "判断栈是否非空StackEmpty的测试:" << endl;
	if (StackEmpty(&stack))cout << "Yes,it's empty!" << endl;
	else cout << "No,it's not empty!" << endl;
	cout << endl;
	/*测试一下入栈操作*/
	int i;
	cout << "测试入栈Push,我们随便弄5个数字进去" << endl;
	for (i = 1; i <= 5; i++)
	{
		Push(&stack, rand() % 100 + 1);/*rand() % 100 + 1是1-100之间的随机数*/
	}
	cout << endl;
	cout << "我们来打印一下这个栈目前的数字情况" << endl;
	for (i = 1; i <= stack.top; i++)
		cout << stack.arr[i]<<"  ";
	cout << endl;
	cout << endl;
	/*测试一下出栈操作*/
	cout << "测试出栈Push,我们就删除三个数字并把它们打印出来" << endl;
	for (i = 0; i <3; i++)
		cout << Pop(&stack) << "  ";
}

主函数

#include <stdio.h>
#include "栈.h"

int main()
{
	Teststack();
	getchar();
	return 0;
}

运行结果:

《算法导论》第十章——基本数据结构(栈、队列、链表)


  

队列

是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作。队列先进先出,就跟排队一样。

我们来看一下队列有哪些要素。队列首先有对头head与队尾tail。然后是以下这些函数:

①初始化这个队列 InitQueue()   使head-1与tail 0
①判断队列是否为空QueueEmpty()   head=-1与tail =0
②判断队列是否已满QueueFull()   (tail+1)%总数=head
③入队EnQueue()
  1、如果队列为空,arr[0]=data,并使head=0
  2、如果队列非空,tail+1,arr[tail]=data
④出队DeQueue()
  1、如果只有一个数据(head=tail),返回这个数据,初始化数组
  2、如果有多个数据,返回arr[head],并使head+1

《算法导论》第十章——基本数据结构(栈、队列、链表)

接下来是代码部分(建议复制后自己运行一下):

队列.h

#pragma once
typedef int dataType;
#define MAX_QUEUE_SIZE 10

struct Queue
{
	int head;
	int tail;
	dataType arr[MAX_QUEUE_SIZE];
};

/*初始化这个队列,使head与tail指向0*/
void InitQueue(Queue *queue);

/*①判断队列是否为空QueueEmpty()  head与tail都指向0*/
bool QueueEmpty(Queue *queue);
	
/*②判断队列是否已满QueueFull()   (tail + 1) % 总数 = head*/
bool QueueFull(Queue *queue);

/*③入队EnQueue()*/
void EnQueue(Queue *queue,dataType data);

/*④出队DeQueue()*/
dataType DeQueue(Queue *queue);

/*队列测试函数*/
void TestQueue();

队列.cpp

#include "队列.h"
#include <stdlib.h>
#include <time.h>
#include <iostream>
using namespace std;

/*初始化这个队列*/
void InitQueue(Queue *queue)
{
	(*queue).head = -1;
	(*queue).tail = 0;
}

/*①判断队列是否为空QueueEmpty()  head与tail都指向0*/
bool QueueEmpty(Queue *queue)
{
	if (((*queue).head == -1) && ((*queue).tail == 0))
		return true;
	else return false;
}

/*②判断队列是否已满QueueFull()   (tail + 1) % 总数 = head*/
bool QueueFull(Queue *queue)
{
	if ((*queue).head == ((*queue).tail + 1) % MAX_QUEUE_SIZE)
		return true;
	else return false;
}

/*③入队EnQueue()*/
void EnQueue(Queue *queue, dataType data)
{
	if (QueueFull(queue))/*如果队列已满*/
	{
		cout << "这个队列已经满了!" << endl;
		return;
	}
	else
	{
		if (QueueEmpty(queue))
		{
			(*queue).arr[0] = data;
			(*queue).head = 0;
		}
		else
		{
			/*使tail向后进一步,若是arr[MAX_QUEUE_SIZE-1]已经有了,则放到arr[0]*/
			(*queue).tail = ((*queue).tail + 1) % MAX_QUEUE_SIZE;
			(*queue).arr[(*queue).tail] = data;
		}
	}
}

/*④出队DeQueue()*/
dataType DeQueue(Queue *queue)
{
	if (QueueEmpty(queue))/*判断是否为空*/
	{
		cout << "队列是空的!" << endl;
		return 0;
	}
	else
	{
		int temp;
		if ((*queue).head == (*queue).tail)/*表明只有一个数*/
		{ 
			/*直接返回这个数并且初始化队列*/
			temp = (*queue).arr[(*queue).head];
			InitQueue(queue);
			return temp;
		}
		else/*如果不止一个数*/
		{
			/*返回head所在的数,head向前一步*/
			temp = (*queue).arr[(*queue).head];
			(*queue).head = ((*queue).head + 1) % MAX_QUEUE_SIZE;
			return temp;
		}
	}
}

/*队列测试函数*/
void TestQueue()
{
	srand((unsigned)time(NULL));/*建立随机种子*/
	struct Queue queue;
	int i;

	InitQueue(&queue);/*初始化队列*/

	cout << "①测试一下 判断非空 这个函数" << endl;
	cout << "测试结果:";
	if (QueueEmpty(&queue))cout << "It's empty!" << endl;
	else cout << "It's not empty!" << endl;
	cout << endl;

	cout << "②测试一下 入队 这个函数" << endl;
	cout << "我们插入8个数字后,我们的队列为:";
	for (i = 0; i < 8; i++)/*插入*/
	{
		EnQueue(&queue, rand() % 100 + 1);
	}
	for (i = 0; i < 8; i++)/*输出*/
	{
		cout << queue.arr[i] << "  ";
	}
	cout << endl;
	cout << endl;

	cout << "③测试一下 出队 这个函数" << endl;
	cout << "取出三个数字并输出它们:";
	for (i = 0; i < 3; i++)
	{
		cout << DeQueue(&queue) << "  ";
	}
	cout << endl;
	cout << endl;
	
	cout << "④测试一下 对满 这个函数" << endl;
	cout << "再插入5个数字后:" << endl;
	for (i = 0; i < 5; i++)
	{
		EnQueue(&queue, rand() % 100 + 1);
	}
	i = queue.head;
	do
	{
		if (i == MAX_QUEUE_SIZE)i = 0;
		cout << queue.arr[i] << "  ";
		i++;
	} while (i != queue.tail+1);
	cout << endl;
	if (QueueFull(&queue))cout << "It's full!" << endl;
	else cout << "It's not full!" << endl;
}

主函数

#include <stdio.h>
#include "队列.h"

int main()
{
	TestQueue();
	getchar();
	return 0;
}

运行结果:

《算法导论》第十章——基本数据结构(栈、队列、链表)

10.2链表


《算法导论》第十章——基本数据结构(栈、队列、链表)

没有哨兵的双向链表

①链表的搜索ListSearch()
  1、建立一个指向链表表头的指针x
  2、只要x非空并且x的key与要搜索的num不等,就让x指向下一位
  3、返回这个x
②链表的插入ListInsert()
  1、让x的下一位指向链表的表头
  2、如果链表非空,就把表头的prev指针指向x
  3、把x赋给链表的表头,并使x的prev指针指向NULL
③链表的删除ListDelete()
  1、如果x不是表头,就让x的前一位指向x的后一位
  2、如果x是表头,就让x的后一位成为表头
  3、如果x不是表尾,就让x的后一位指向x的前一位
④打印列表ListPrint()
  1、新建一个指针p指向链表表头
  2、只要p->next非空,就打印p->key,并使p指向p->next

接下来是代码部分(建议复制后自己运行一下):

没有哨兵的双向链表.h

#pragma once
#define LEN sizeof(struct List)
struct List
{
	struct List *prev;	/*指向前一个结点*/
	struct List *next;	/*指向后一个结点*/
	struct List *head;
	int key;
};

/*查找数据*/
struct List* ListSearch(struct List list, int num);

/*链表的插入*/
struct List* ListInsert(struct List list, struct List *x);

/*链表的删除*/
struct List* ListDelete(struct List list, struct List *x);

/*打印列表*/
void ListPrint(struct List list);

/*链表测试函数*/
void ListTest();

没有哨兵的双向链表.cpp

#include "没有哨兵的双向链表.h"
#include <stdlib.h>
#include <time.h>
#include <iostream>
using namespace std;

/*查找数据*/
struct List* ListSearch(struct List list, int num)
{
	struct List *x;
	x = list.head;
	while (x != NULL && x->key != num)/*x非空并且x的key与要搜索的num不等*/
	{
		x = x->next;
	}
	return x;
}

/*链表的插入*/
struct List* ListInsert(struct List list, struct List *x)
{
	x->next = list.head;
	if (list.head != NULL)/*如果链表非空*/
	{
		list.head->prev = x;/*把x赋给链表的第一位*/
	}
	list.head = x;
	x->prev = NULL;/*x前一个结点为空*/
	return x;
}

/*链表的删除*/
struct List* ListDelete(struct List list, struct List *x)
{
	if (x->prev != NULL)/*x不是表头*/
	{
		x->prev->next = x->next;/*让x的前一位指向x的后一位*/
	}
	else/*x是表头*/
	{
		list.head = x->next;/*让x的下一位成为表头*/
	}
	if (x->next != NULL)/*x不是表尾*/
	{
		x->next->prev = x->prev;/*让x的后一位指向x的前一位*/
	}
	return list.head;
}

/*打印列表*/
void ListPrint(struct List list)
{
	struct List *p = list.head;/*p是一个指向表头的指针*/
	while (p)/*只要p不指向NULL*/
	{
		cout << p->key << "  ";/*就打印p的数据*/
		p = p->next;/*让p指向它的下一位*/
	}
	cout << endl;
	cout << endl;
}

/*链表测试函数*/
void ListTest()
{
	struct List list, *x, *t;/*建立一个链表,和两个指针*/
	int i;
	srand((unsigned)time(NULL));/*建立随机种子*/
	list.head = NULL; x = NULL; t = NULL;

	for (i = 0; i < 10; i++)
	{
		x = (struct List*)malloc(LEN);/*开辟一个新的结点*/
		x->key = rand() % 100 + 1;/*x->key的值在1-100之间*/
		if (i == 6)
			t = x;/*t为链表第7个结点*/
		list.head = ListInsert(list, x);/*把x依次插入到链表中*/
	}

	cout << "打印链表:";
	ListPrint(list);
	x = ListSearch(list, 60);/*x是60所在的结点*/
	if (x == NULL)
		cout << "Can't find the data!" << endl;
	else
		cout << "The number 60 has been found!" << endl;
	cout << endl;

	cout << "我们现在删除第7个进入的元素(也就是第四个):" << endl;
	ListDelete(list, t);
	cout << "打印链表:";
	ListPrint(list);
}

主函数

#include <stdio.h>
#include "没有哨兵的双向链表.h"

int main()
{
	ListTest();
	getchar();
	return 0;
}

运行结果:

《算法导论》第十章——基本数据结构(栈、队列、链表)

有哨兵的双向链表

  **哨兵**是一个哑对象,其作用是简化边界处理条件。我对哨兵的作用其实并不是特别特别清除,我也为此查找了一些网上的文章,但是感觉要么看不懂,要么模棱两可,这样我就干脆只记住:哨兵就是一个使我们的代码更简洁、更加容易理解的存在,就好了。

我们来看一下书上对哨兵的图示:

《算法导论》第十章——基本数据结构(栈、队列、链表)


按照书中对它的描述,我大概也画了一幅关系图:

《算法导论》第十章——基本数据结构(栈、队列、链表)


  一开始并看不懂struct NilList* InsertList(struct NilList &list, struct NilList *x)这里的那个&list,于是我查了一下,这是一个引用传递。引用传递是将变量的内存地址传递给方法,方法操作变量时会找到保存在该地址的变量,对其进行操作。会对原变量造成影响。

有哨兵的双向链表.h

#pragma once
#define NILLEN sizeof(struct NilList)

struct NilList
{
	struct NilList *prev;	/*指向前一个结点*/
	struct NilList *next;	/*指向后一个结点*/
	struct NilList *nil;	/*哨兵*/
	int key;
};

/*初始化链表*/
struct NilList* InitList(struct NilList &list);

/*查找数据*/
struct NilList* SearchList(struct NilList &list, int num);

/*链表的插入*/
struct NilList* InsertList(struct NilList &list, struct NilList *x);

/*链表的删除*/
struct NilList* DeleteList(struct NilList &list, struct NilList *x);

/*打印列表*/
void PrintList(struct NilList list);

/*链表测试函数*/
void TestList();

有哨兵的双向链表.cpp

#include "有哨兵的双向链表.h"
#include <stdlib.h>
#include <time.h>
#include <iostream>
using namespace std;

/*初始化链表*/
struct NilList* InitList(struct NilList &list)
{
	list.nil = (struct NilList*)malloc(NILLEN);/*为哨兵开辟一个结点*/
	list.nil->next = list.nil;/*让哨兵的尾指针指向本身*/
	list.nil->prev = list.nil;/*让哨兵的头指针指向本身*/
	return list.nil;
}

/*查找数据*/
struct NilList* SearchList(struct NilList &list, int num)
{
	struct NilList *x;
	x = list.nil->next;/*让x指向链表头部*/
	while (x != list.nil && x->key!=num)
	{
		x = x->next;
	}
	return x;
}

/*链表的插入*/
struct NilList* InsertList(struct NilList &list, struct NilList *x)
{
	x->next = list.nil->next;
	list.nil->next->prev = x;/*哨兵->next(表头x)->prev->哨兵*/
	list.nil->next = x;/*哨兵->next(表头x)*/
	x->prev = list.nil;/*(表头x)->prev->哨兵*/
	return list.nil;
}

/*链表的删除*/
struct NilList* DeleteList(struct NilList &list, struct NilList *x)
{
	x->next->prev = x->prev;/*x的后一位指向x的前一位*/
	x->prev->next = x->next;/*x的前一位指向x的后一位*/
	return list.nil;
}

/*打印列表*/
void PrintList(struct NilList list)
{
	struct NilList *p = list.nil->next;
	if (p != list.nil)
	{
		while (p != list.nil)
		{
			cout << p->key << "  ";
			p = p->next;
		}
		cout << endl;
	}
	else
	{
		cout << "It's empty!" << endl;
	}
	cout << endl;
}

/*链表测试函数*/
void TestList()
{
	struct NilList list, *x, *t;/*建立一个链表,和两个指针*/
	int i;
	InitList(list);/*初始化这个链表*/
	PrintList(list);
	srand((unsigned)time(NULL));
	x = NULL; t = NULL;

	for (i = 0; i < 10; i++)
	{
		x = (struct NilList*)malloc(NILLEN);/*开辟一个新的结点*/
		x->key = rand() % 100 + 1;/*x->key的值在1-100之间*/
		if (i == 6)
			t = x;/*t为链表第7个结点*/
		list.nil=InsertList(list, x);/*把x依次插入到链表中*/
	}

	cout << "打印链表:";
	PrintList(list);
	x = SearchList(list, 60);/*x是60所在的结点*/

	if (x ==list.nil)
		cout << "Can't find the data!" << endl;
	else
		cout << "The number 60 has been found!" << endl;
	cout << endl;

	cout << "我们现在删除第7个进入的元素(也就是第四个):" << endl;
	DeleteList(list, t);
	cout << "打印链表:";
	PrintList(list);
}

主函数

#include <stdio.h>
#include "有哨兵的双向链表.h"

int main()
{
	TestList();
	getchar();
	return 0;
}

运行结果:

《算法导论》第十章——基本数据结构(栈、队列、链表)

《算法导论》第十章——基本数据结构(栈、队列、链表)

10.3指针和对象的实现

  当有些语言不支持指针和对象数据类型时,我们利用数组和数组下标构造对象和指针。这种链表称为静态链表

10.4有根树的介绍

  跟二叉树一样,二叉树是左儿子,右儿子,这个是左儿子右兄弟具体的我推荐一篇文章:Flammable_ice的文章(https://blog.csdn.net/z84616995z/article/details/19202773)写的非常好!

参考网站以及博客:

百度百科
数据结构(八)——栈
http://blog.51cto.com/9291927/2063393
算法导论第十章基本数据结构(写的很不错)
https://blog.csdn.net/z84616995z/article/details/19202773
c语言结构体struct相关使用说明
https://blog.csdn.net/cynophile/article/details/79062494