数据结构与算法

文章目录

第一章 绪论

(1)数据结构:是指相互之间存在着一种或多种关系的数据的结合,是数据元素之间的一种关系。
(2)数据类型:是指一个值的集合以及在这些值上定义的一组操作的集合;每种数据类型都可以看做一种数据结构。
(3)抽象数据类型:是指抽象数据的组织和与之相关的操作,可看做是数据的逻辑结构及其在逻辑结构上定义的操作。
(4)数据结构的存储结构:顺序存储,链式存储,索引存储,散列存储。
(5)算法的概念及特性:是对特定问题求解步骤的一种描述,是指令的有限序列。算法具有有穷性、确定性、可行性、有输入和输出的特性。
(6)算法的设计要求:正确性、可读性、健壮性、高效性。
(7)算法描述:自然语言、流程图、盒图(N-S图)、类高级程序设计语言。
(8)算法的时间复杂度:O(1)<O(log2n)<O(n)<O(nlog2n)<O(n2)<O(n3)<O(2^n)。
(9)算法的空间复杂度:算法耗费的存储空间。
(10)递归算法:一种直接或间接地调用自身算法的过程。
(11)递归调用的特点:每次调用在规模上都有所缩小;相邻两次调用有紧密的联系,前一次要为后一次做准备,递归调用都是有结束条件的。
(12)递归的优缺点:优点:结构清晰,可读性强,容易用数学归纳法来证明算法的正确性。缺点:运行效率较低,时间和空间耗费比非递归算法大。(13)同一个算法,实现语言的级别越高,执行效率就越低。
(14)从逻辑上,可以把数据结构分为线性结构和非线性结构两大类。

第二章 基本线性结构

头结点和头指针

(1)头指针:
–头指针是指链表指向第一个结点的指针,若链表有头结点,则是指向头结点的指针
–头指针具有标识作用,所以头指针冠以链表的名字(指针变量的名字)
–无论链表是否为空,头指针均不为空
–头指针是链表的必要元素
(2)头结点:
–头结点是为了操作的统一和方便而设立的,放在第一个元素的结点之前,其数据域一般无意义(但也可以用来存放链表的长度)
–有了头结点,对在第一元素结点前插入结点和删除第一结点起操作与其它结点的操作就统一了
–头结点不一定是链表的必要元素

顺序表的基本操作

/*
CreateList(SqList &L,int n) 参数:顺序表L,顺序表长度n 功能:创建长度为的顺序表 时间复杂度:O(n)
InitList(SqList &L) 参数:顺序表L 功能:初始化 时间复杂度:O(1)
InsertList(SqList &L,int i,ElemType e) 参数:顺序表L,位置i,元素e 功能:位置i处插入元素e 时间复杂度:O(n)
ListDelete(SqList &L,int i) 参数:顺序表L,位置i 功能:删除位置i处元素 时间复杂度:O(n)
LocateElem(SqList L,ElemType e) 参数:顺序表L,元素e 功能:返回第一个等于e的元素的位置 时间复杂度:O(n)
Reverse(SqList &L) 参数:顺序表L 倒置函数 将原顺序表直接倒置
PrintList(SqList L) 参数:顺序表L 功能:遍历L,并输出
ClearList(SqList &L) 参数:顺序表L 功能:清空顺序表
*/
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<iostream>
#define MaxSize 10005
#define ElemType int
#define Status int
using namespace std;

//顺序表数据结构
typedef struct
{
	ElemType data[MaxSize];//顺序表元素
	int length;            //顺序表当前长度
}SqList;
//***************************基本操作函数*******************************//
//初始化顺序表函数,构造一个空的顺序表 

Status InitList(SqList& L)
{
	memset(L.data, 0, sizeof(L));//初始化数据为0
	L.length = 0;                //初始化长度为0
	return 0;
}
//创建顺序表函数 初始化前n个数据
bool CreateList(SqList& L, int n)
{
	if (n<0 || n>MaxSize)false;//n非法
	for (int i = 0; i < n; i++)
	{
		scanf("%d", &L.data[i]);
		L.length++;
	}
	return true;
}
//插入函数 位置i插入数据 i及之后元素后移  1=<i<=length+1 
bool InsertList(SqList& L, int i, ElemType e)
{
	if (i<1 || i>L.length + 1) //判断位置是否有效
	{
		printf("位置无效!!!\n");
		return false;
	}
	if (L.length >= MaxSize)//判断存储空间是否已满
	{
		printf("当前存储空间已满!!!\n");
		return false;
	}
	for (int j = L.length; j >= i; j--)//位置i及之后元素后移
	{
		L.data[j] = L.data[j - 1];
	}
	L.data[i - 1] = e;
	L.length++;
	return true;
}
//删除函数 删除位置i的元素 i之后的元素依次前移
bool  ListDelete(SqList& L, int i)
{
	if (i<1 || i>L.length)
	{
		printf("位置无效!!!\n");
		return false;
	}
	for (int j = i; j <= L.length - 1; j++)//位置i之后元素依次前移覆盖
	{
		L.data[j - 1] = L.data[j];
	}
	L.length--;
	return true;
}
//查找函数 按位置从小到大查找第一个值等于e的元素 并返回位置
int LocateElem(SqList L, ElemType e)
{
	for (int i = 0; i < L.length; i++)//从低位置查找
	{
		if (L.data[i] == e)
			return i + 1;
	}
	return 0;
}
//输出功能函数 按位置从小到大输出顺序表所有元素
void PrintList(SqList L)
{
	printf("当前顺序表所有元素:");
	for (int i = 0; i < L.length; i++)
	{
		printf("%d ", L.data[i]);
	}
	printf("\n");
}
//倒置函数 将原顺序表直接倒置
void Reverse(SqList& L)
{
	if (L.length)
		for (int i = 0; i < L.length - 1 - i; i++)
		{
			int t = L.data[i];
			L.data[i] = L.data[L.length - 1 - i];
			L.data[L.length - 1 - i] = t;
		}
}
//清空顺序表
void ClearList(SqList& L) {
	L.length = 0;
}
//********************************功能函数*****************************************//
//创建顺序表函数
void Create(SqList& L)
{
	int n; bool flag;
	L.length = 0;
	printf("请输入要创建的顺序表长度(>1):");
	scanf("%d", &n);
	printf("请输入%d个数(空格隔开):", n);
	flag = CreateList(L, n);
	if (flag) {
		printf("创建成功!\n");
		PrintList(L);
	}
	else printf("输入长度非法!\n");

}
//插入功能函数 调用InsertList完成顺序表元素插入 调用PrintList函数显示插入成功后的结果
void Insert(SqList& L)
{
	int place; ElemType e; bool flag;
	printf("请输入要插入的位置(从1开始)及元素:\n");
	scanf("%d%d", &place, &e);
	flag = InsertList(L, place, e);
	if (flag)
	{
		printf("插入成功!!!\n");
		PrintList(L);
	}
}
//删除功能函数 调用ListDelete函数完成顺序表的删除 调用PrintList函数显示插入成功后的结果
void Delete(SqList& L)
{
	int place; bool flag;
	printf("请输入要删除的位置(从1开始):\n");
	scanf("%d", &place);
	flag = ListDelete(L, place);
	if (flag)
	{
		printf("删除成功!!!\n");
		PrintList(L);
	}
}
//查找功能函数 调用LocateElem查找元素
void Search(SqList L)
{
	ElemType e; int flag;
	printf("请输入要查找的值:\n");
	scanf("%d", &e);
	flag = LocateElem(L, e);
	if (flag)
	{
		printf("该元素位置为:%d\n", flag);
	}
	else
		printf("未找到该元素!\n");
}
//菜单
void menu()
{
	printf("********1.创建                        2.插入*********\n");
	printf("********3.删除                        4.查找*********\n");
	printf("********5.倒置                        6.输出*********\n");
	printf("********7.清空                        8.退出*********\n");
}
int main()
{
	SqList L; int choice;
	InitList(L);
	while (1)
	{
		menu();
		printf("请输入菜单序号:\n");
		scanf("%d", &choice);
		if (8 == choice) break;
		switch (choice)
		{
		case 1:Create(L); break;
		case 2:Insert(L); break;
		case 3:Delete(L); break;
		case 4:Search(L); break;
		case 5:Reverse(L); break;
		case 6:PrintList(L); break;
		case 7:ClearList(L); break;
		default:printf("输入错误!!!\n");
		}
	}
	return 0;
}

单链表的基本操作

/*
    LinkList Creat_List1(LinkList& L) 参数:单链表L 功能:初始化 头插法建立单链表
	LinkList Creat_List2(LinkList& L) 参数:单链表L 功能:初始化 尾插法建立单链表
	InitList(LinkList &L) 参数:单链表L 功能:初始化 时间复杂度 O(1)
	ListLength(LinkList L) 参数:单链表L 功能:获得单链表长度 时间复杂度O(n)
	ListInsert(LinkList &L,int i,ElemType e) 参数:单链表L,位置i,元素e 功能:位置i后插 时间复杂度O(n)[加入了查找]已知指针p指向的后插 O(1)
	ListDelete(LinkList &L,int i) 参数:单链表L,位置i 功能:删除位置i元素 时间复杂度O(n)[加入了查找]若已知p指针指向的删除最好是O(1),因为可以与后继结点交换数据域,然后删除后继结点。  最坏是O(n),即从头查找p之前的结点,然后删除p所指结点
	LocateElem(LinkList L,ElemType e) 参数:单链表L,元素e 功能:查找第一个等于e的元素,返回指针 时间复杂度O(n)
*/
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<iostream>
using namespace std;
#define Status int
#define ElemType int
//单链表结点数据结构
typedef struct LNode
{
	ElemType data;//数据域
	struct LNode* next;//指针域
}LNode, * LinkList;
//**************************基本操作函数***************************//
//头插法建立单链表
LinkList Create_List1(LinkList& L)
{
	LinkList s;
	int x;
	L = (LNode*)malloc(sizeof(LNode));
	L->next = NULL;
	scanf("%d", &x);
	while (x != 999)
	{
		s = (LNode*)malloc(sizeof(LNode));
		s->data = x;
		s->next = L->next;
		L->next = s;
		scanf("%d", &x);
	}
	return L;
}
//尾插法建立单链表
LinkList Create_List2(LinkList &L)
{
	int x;
	L = (LNode*)malloc(sizeof(LNode));
	LNode* s, * r = L;
	scanf("%d", &x);
	while (x != 999) 
	{
		s = (LNode*)malloc(sizeof(LNode));     //创建新的结点 
		s->data = x;
		r->next = s;
		r = s;
		scanf("%d", &x);
	}
	r->next = NULL;
	return L;
}

//获取单链表长度
int ListLength(LinkList L)
{
	LinkList p = L;
	int sum = 0;
	while (p->next)
	{
		sum++;
		p = p->next;
	}
	return sum;
}
//插入函数--后插法 插入到第i(1<=i<=length+1)个位置 即i-1之后 不必区分i的位置
bool ListInsert(LinkList& L, int i, ElemType e)
{
	LNode* s;
	LinkList p = L;
	int j = 0;
	while (p && (j < i - 1))//j指到i-1位置或者p已经到最后时跳出
	{
		p = p->next;
		++j;
	}
	if (!p)
	{
		printf("插入位置无效!!!\n");
		return false;
	}
	s = new LNode;
	s->data = e;
	s->next = p->next;
	p->next = s;
	return true;
}
//删除函数 删除位置i的结点 即删除i-1之后的结点
bool ListDelete(LinkList& L, int i)
{
	LNode* s;
	LinkList p = L;
	int j = 0;
	LinkList q;
	while (p && (j < i - 1))//j指到i-1位置
	{
		p = p->next;
		++j;
	}
	if (p == NULL)
	{
		printf("删除位置无效!!!\n");
		return false;
	}
	q = p->next;
	p->next = q->next;
	free(q);//释放空间
	return true;
}
//查找函数 按值查找 查找第一个等于e的结点 成功返回该结点指针,否则返回NULL
LNode* LocateElem(LinkList L, ElemType e)
{
	LNode* p = L;
	while (p && (p->data != e))
	{
		p = p->next;
	}
	return p;
}
//**************************功能实现函数**************************//
//遍历输出函数
void PrintList(LinkList L)
{
	LinkList p = L->next;//跳过头结点
	if (ListLength(L))
	{
		printf("当前单链表所有元素:");
		while (p)
		{
			printf("%d ", p->data);
			p = p->next;
		}
		printf("\n");
	}
	else
	{
		printf("当前单链表已空!\n");
	}
}
//插入功能函数 调用ListInsert后插
void Insert(LinkList& L)
{
	int place;
	ElemType e;
	bool flag;
	printf("请输入要插入的位置(从1开始)及元素:\n");
	scanf("%d%d", &place, &e);
	flag = ListInsert(L, place, e);
	if (flag)
	{
		printf("插入成功!!!\n");
		PrintList(L);
	}
}
//删除功能函数 调用ListDelete删除
void Delete(LinkList L)
{
	int place;
	bool flag;
	printf("请输入要删除的位置(从1开始):\n");
	scanf("%d", &place);
	flag = ListDelete(L, place);
	if (flag)
	{
		printf("删除成功!!!\n");
		PrintList(L);
	}
}
//查找功能函数 调用LocateElem查找
void Search(LinkList L)
{
	ElemType e;
	LNode* q;
	printf("请输入要查找的值:\n");
	scanf("%d", &e);
	q = LocateElem(L, e);
	if (q)
	{
		printf("找到该元素!\n");
	}
	else
		printf("未找到该元素!\n");
}
//菜单
void menu()
{
	printf("********1.后插    2.删除*********\n");
	printf("********3.查找    4.输出*********\n");
	printf("********5.退出          *********\n");
}
//主函数
int main()
{
	LinkList L;
	int choice;
	printf("尾插法建立单链表:\n");
	L = Create_List2(L);
	PrintList(L);
	while (1)
	{
		menu();
		printf("请输入菜单序号:\n");
		scanf("%d", &choice);
		if (choice == 5) break;
		switch (choice)
		{
		case 1:Insert(L); break;
		case 2:Delete(L); break;
		case 3:Search(L); break;
		case 4:PrintList(L); break;
		default:printf("输入错误!!!\n");
		}
	}
	return 0;
}

单链表的应用(单链表逆置和删除重复节点)

#include<iostream>
#include<cstdbool>
#include<cstring>
using namespace std;

typedef struct LNode
{
	int data;
	struct LNode* next;
}*LinkList,LNode;

LinkList Create_List(LinkList& L)
{
	LinkList s;
	int x;
	L = (LNode*)malloc(sizeof(LNode));
	L->next = NULL;
	scanf("%d", &x);
	while (x != 999)
	{
		s = (LNode*)malloc(sizeof(LNode));
		s->data = x;
		s->next = L->next;
		L->next = s;
		scanf("%d", &x);
	}
	return L;
}

void reverse(LinkList& L)
{
	LNode* p, * q;
	p = L->next;
	L->next = NULL;
	while (p)
	{
		q = p;
		p = p->next;
		q->next = L->next;
		L->next = q;
	}
}
int ListLength(LinkList L)
{
	LinkList p = L;
	int sum = 0;
	while (p->next)
	{
		sum++;
		p = p->next;
	}
	return sum;
}

void PrintList(LinkList L)
{
	LinkList p = L->next;//跳过头结点
	if (ListLength(L))
	{
		printf("当前单链表所有元素:");
		while (p)
		{
			printf("%d ", p->data);
			p = p->next;
		}
		printf("\n");
	}
	else
	{
		printf("当前单链表已空!\n");
	}
}
//删除重复节点
void deleterep(LinkList& L)
{
	LNode* p, * q, * r;
	p = L->next;
	if (p == NULL)
		return;
	while (p->next)
	{
		q = p;
		while (q->next)
		{
			if (q->next->data == p->data)
			{
				r = q->next;
				q->next = r->next;
				free(r);
			}
			else
				q = q->next;
		}
		p = p->next;
	}
}

int main()
{
	cout << "创建单链表:(输入999结束)" << endl;
	LinkList L;
	L= Create_List(L);
	PrintList(L);
	reverse(L);
	cout << "逆置后的单链表:" << endl;
	PrintList(L);
	cout << "删除重复节点后的单链表:" << endl;
	deleterep(L);
	PrintList(L);
	return 0;
}

循环链表

对两个单循环链表H1,H2的连接操作,是将H2的第一个数据结点接到H1的尾结点,如用头指针表示,则需找到第一个链表的尾结点,时间复杂度为O(n),而链表若用尾指针R1,R2标识,时间复杂度为O(1)。
核心代码:
p=R1->next;
R1->next=R2->next->next;
free(R2->next);
R2->next=p;
在这里插入图片描述

双向链表

一个等式

p->prior->next=p=p->next->prior;
在这里插入图片描述

在双向链表中插入一个节点

前插

设p指向双向链表中某结点,s指向待插入的值为x的新结点,将s插入到p的前面。操作如下:
①s->prior=p->prior;
②p->prior->next=s;
③s->next=p;
④p->prior=s;
在这里插入图片描述
上面指针操作的顺序不是唯一的,但也不是任意的,操作①必须要放到操作④的前面完成,否则*p的前驱结点的指针就丢掉了。

后插

额。。图就不画了
s->next=p->next;
s->prior=p;
p->next=s;
s->next->prior=s;

在双向链表中删除指定结点

设p指向双向链表中某结点,删除*p。
操作如下:①p->prior->next=p->next;
②p->next->prior=p->prior;
③free§;
在这里插入图片描述

&p,p,*p

&p:指针p本身的地址
p:指向的内存单元
*p:指向的内存单元的内容

顺序表和链表的比较

顺序存储有3个优点:
(1) 方法简单,各种高级语言中都有数组,容易实现。
(2) 不用为表示结点间的逻辑关系而增加额外的存储开销。
(3) 顺序表具有按元素序号随机访问的特点。
顺序存储有2个缺点:
  (1) 在顺序表中做插入删除操作时,平均移动大约表中一半的元素,因此对n较大的顺序表效率低。
(2) 需要预先分配足够大的存储空间,估计过大,可能会导致顺序表后部大量闲置;预先分配过小,又会造成溢出。
链表的优缺点恰好与顺序表相反。

堆栈

顺序栈的基本操作

/*
	InitStack(SqStack &s) 参数:顺序栈s 功能:初始化  时间复杂度O(1)
	Push(SqStack &s,SElemType e) 参数:顺序栈s,元素e 功能:将e入栈 时间复杂度:O(1)
	Pop(SqStack &s,SElemType &e) 参数:顺序栈s,元素e 功能:出栈,e接收出栈元素值 时间复杂度O(1)
	GetTop(SqStack s,SElemType &e) 参数:顺序栈s,元素e 功能:得到栈顶元素 时间复杂度O(1)
*/
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<stack>
#include<iostream>
using namespace std;
#define Status int
#define SElemType int
#define MaxSize 10005
//栈数据结构
typedef struct Stack
{
	SElemType* base;//栈底指针不变
	SElemType* top;//栈顶指针 一直在栈顶元素上一个位置
	int stacksize;//栈可用的最大容量
}SqStack;
//**************************************基本操作函数************************************//
//初始化函数
Status InitStack(SqStack& s)
{
	s.base = new SElemType[MaxSize];//动态分配最大容量
	if (!s.base)
	{
		printf("分配失败\n");
		return 0;
	}
	s.top = s.base;//栈顶指针与栈底相同
	s.stacksize = MaxSize;
	return 1;
}
//入栈
Status Push(SqStack& s, SElemType e)
{
	if (s.top - s.base == s.stacksize) return 0;//栈满
	*s.top++ = e;//先入栈,栈顶指针再上移 
	return 1;
}

//出栈 用e返回值
Status Pop(SqStack& s, SElemType& e)
{
	if (s.top == s.base) return 0;//栈空
	e = *--s.top;//先减减 指向栈顶元素,再给e
	return 1;
}
//打印栈内元素
void PrintStack(SqStack& s)
{
	cout << "栈内元素为:" << endl;
	int e;
	while (s.top - s.base != 0)
	{
		Pop(s, e);
		cout << e << " ";
	}
	cout << endl;
}

//得到栈顶元素
bool GetTop(SqStack s, SElemType& e) 
{
	if (s.top == s.base) return false;//栈空			
	else e = *--s.top;
	return true;
}
//********************************功能实现函数**************************************//
//菜单
void menu()
{
	printf("********1.入栈      2.出栈*********\n");
	printf("********3.取栈顶    4.退出*********\n");
}
//入栈功能函数 调用Push函数
void PushToStack(SqStack& s)
{
	int n;
	SElemType e;
	int flag;
	printf("请输入入栈元素个数(>=1):\n");
	scanf("%d", &n);
	for (int i = 0; i < n; i++)
	{
		printf("请输入第%d个元素的值:", i + 1);
		scanf("%d", &e);
		flag = Push(s, e);
		if (flag)
			printf("%d已入栈\n", e);
		else
		{
			printf("栈已满!!!\n");
			break;
		}
	}
}
//出栈功能函数 调用Pop函数
void PopFromStack(SqStack& s)
{
	int n;
	SElemType e;
	int flag;
	printf("请输入出栈元素个数(>=1):\n");
	scanf("%d", &n);
	for (int i = 0; i < n; i++)
	{
		flag = Pop(s, e);
		if (flag)
			printf("%d已出栈\n", e);
		else
		{
			printf("栈已空!!!\n");
			break;
		}
	}
}
//取栈顶功能函数 调用GetTop
void GetTopOfStack(SqStack& s)
{
	SElemType e;
	bool flag;
	flag = GetTop(s, e);
	if (flag)
		printf("栈顶元素为:%d\n", e);
	else
		printf("栈已空!!!\n");
}

//主函数
int main()
{
	SqStack s; 
	int choice;
	InitStack(s);
	while (1)
	{
		menu();
		printf("请输入菜单序号:\n");
		scanf("%d", &choice);
		if (choice == 4) break;
		switch (choice)
		{
		case 1:PushToStack(s); break;
		case 2:PopFromStack(s); break;
		case 3:GetTopOfStack(s); break;
		default:printf("输入错误!!!\n");
		}
	}
	PrintStack(s);
	return 0;
}

链栈的基本操作

在这里插入图片描述

/*
	InitStack(LinkStack &S) 参数:链栈S 功能:初始化  时间复杂度O(1)
	Push(LinkStack &S,SElemType e) 参数:链栈S,元素e 功能:将e入栈 时间复杂度:O(1)
	Pop(LinkStack &S,SElemType &e) 参数:链栈S,元素e 功能:栈顶出栈,e接收出栈元素值 时间复杂度O(1)
	GetTop(LinkStack &S,SElemType &e) 参数:链栈S,元素e 功能:得到栈顶元素 时间复杂度O(1)
*/
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<iostream>
using namespace std;
#define Status int
#define SElemType int
//链栈结点数据结构
typedef struct StackNode
{
	SElemType data;//数据域
	struct StackNode* next;//指针域
	int *base, * top;
}StackNode, *LinkStack;
//**************************基本操作函数***************************//
//初始化函数
Status InitStack(LinkStack& S)
{
	S = NULL;//生成空栈 以单链表表头为栈顶 注意,链栈没有像链表似的头结点
	return 1;
}
//入栈函数 将e压入栈(不带头结点)
Status Push(LinkStack& S, SElemType e)
{
	StackNode* p;
	p = new StackNode;//生成新节点
	p->data = e;      //赋值
	p->next = S;      //压入栈顶
	S = p;
	return 1;
}
/*入栈函数(带头结点)
p->data=e;
p->next=top->next;
top->next=p;
*/
//出栈函数  栈顶出栈用e返回 注意释放空间
bool Pop(LinkStack& S, SElemType& e)
{
	LinkStack p;
	if (S == NULL)return false;//栈空
	e = S->data;
	p = S;
	S = S->next;
	free(p);
	return true;
}
//取栈顶函数 用e返回
bool GetTop(LinkStack& S, SElemType &e) 
{
	if (S == NULL) return false;//栈顶为空
	e = S->data;
	return true;
}
//**************************功能实现函数***************************//
//菜单
void menu()
{
	printf("********1.入栈          2.出栈*********\n");
	printf("********3.取栈顶元素    4.退出*********\n");
}
//入栈功能函数 调用Push函数 
void PushToStack(LinkStack& S)
{
	int n;
	SElemType e; 
	int flag;
	printf("请输入入栈元素个数(>=1):\n");
	scanf("%d", &n);
	for (int i = 0; i < n; i++)
	{
		printf("请输入第%d个元素的值:", i + 1);
		scanf("%d", &e);
		flag = Push(S, e);
		if (flag)
			printf("%d已入栈\n", e);
	}
}
//出栈功能函数 调用Pop函数
void PopFromStack(LinkStack& S)
{
	int n;
	SElemType e;
	int flag;
	printf("请输入出栈元素个数(>=1):\n");
	scanf("%d", &n);
	for (int i = 0; i < n; i++)
	{
		flag = Pop(S, e);
		if (flag)
			printf("%d已出栈\n", e);
		else {
			printf("栈已空!!!\n");
			break;
		}
	}
}
//取栈顶功能函数 调用GetTop函数
void GetTopOfStack(LinkStack S)
{
	SElemType e;
	bool flag;
	flag = GetTop(S, e);
	if (flag)printf("栈顶元素为:%d\n", e);
	else printf("栈已空!!!\n");
}
//主函数
int main()
{
	LinkStack S; 
	int choice;
	InitStack(S);
	while (1)
	{
		menu();
		printf("请输入菜单序号:\n");
		scanf("%d", &choice);
		if (choice == 4) break;
		switch (choice)
		{
		case 1:PushToStack(S); break;
		case 2:PopFromStack(S); break;
		case 3:GetTopOfStack(S); break;
		default:printf("输入错误!!!\n");
		}
	}
	return 0;
}

表达式求值

中缀表达式求值

设运算规则为:
(1)运算符的优先级为:( )→ ^ → ×、/、% → +、- ;
(2)有括号出现时先算括号内的,后算括号外的,多层括号,由内向外进行;
(3)乘方连续出现时先算最右面的。
表达式作为一个满足表达式语法规则的串存储,如表达式“32^(4+22-13)-5”,它的的求值过程为:自左向右扫描表达式,当扫描到32时不能马上计算,因为后面可能还有更高的运算。

笔记:(1)左括号未入栈前,优先级是最高的,但入栈后,优先级就是最低的。
(2)右括号不入栈。
(3)若运算符遇到优先级比他高的, 则优先级高的要先出栈。

中缀表达式 “32^(4+22-1*3)-5”求值过程中两个栈的状态情况如图所示。
在这里插入图片描述

后缀表达式求值

运算规则:
(1)无括号和优先级的约束。
(2)只使用一个对象栈。
(3)从左到右扫描。

后缀表达式“32422*+13*-^*5-”,栈中状态变化情况:
在这里插入图片描述

前缀表达式

和后缀表达式唯一区别:从右到左扫描

中缀表达式转为前缀表达式

在这里插入图片描述

中缀表达式转为后缀表达式

在这里插入图片描述

队列

假溢出现象

从图中可以看出,随着入队出队的进行,会使整个队列整体向后移动,这样就出现了d图的现象:队尾指针已经移动到了最后,再有元素入队就会出现溢出,而事实上此时队中并未真的满员,这种现象就叫"假溢出",这是由于受“队尾入、队头出”限制的操作所造成的。为此,采用循环队列来解决假溢出。
在这里插入图片描述

循环队列的基本操作

/*
	InitQueue(SqQueue &Q)              参数:循环队列Q 功能:初始化循环队列Q 时间复杂度:O(1)
	QueueEmpty(SqQueue Q)              参数:循环队列Q 功能:判断队空与否 时间复杂度:O(1)
	EnQueue(SqQueue &Q,QElemType e)    参数:循环队列Q,元素e 功能:使元素e入队 时间复杂度:O(1)
	DeQueue(SqQueue &Q,QElemType &e)   参数:循环队列Q,元素e 功能:队头出队,用e返回值 时间复杂度:O(1)
	GetHead(SqQueue &Q,QElemType &e)   参数:循环队列Q,元素e 功能:获取队头元素e 时间复杂度:O(1)
*/
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<queue>
#include<iostream>
using namespace std;
#define Status int
#define QElemType int
#define MaxQSize 10005
//循环队列数据结构
typedef struct
{
	QElemType data[MaxQSize];//数据域
	int front, rear;          //队头队尾指针
}SqQueue;
//***************************基本操作函数**************************//
//初始化函数
Status InitQueue(SqQueue& Q)
{
	Q.front = Q.rear = 0;
	return 1;
}
//判断队空函数
bool QueueEmpty(SqQueue Q)
{
	if (Q.front != Q.rear)return false;
	else return true;
}
//入队函数
bool EnQueue(SqQueue& Q, QElemType e)
{
	if ((Q.rear + 1) % MaxQSize == Q.front)return false; //队列满
	Q.data[Q.rear] = e;
	Q.rear = (Q.rear + 1) % MaxQSize;//指针加1 取模
	return true;
}
//出队函数
bool DeQueue(SqQueue& Q, QElemType& e)
{
	if (Q.front == Q.rear)return false;//队空
	e = Q.data[Q.front];
	Q.front = (Q.front + 1) % MaxQSize; //指针加1 取模
		return true;
}
//取队头
bool GetHead(SqQueue& Q, QElemType& e)
{
	if (Q.front == Q.rear)return false;//队空
	e = Q.data[Q.front];
	return true;
}
//**************************功能实现函数****************************//
//入队功能函数 调用EnQueue函数
void EnterToQueue(SqQueue& Q)
{
	int n; QElemType e; int flag;
	printf("请输入入队元素个数(>=1):\n");
	scanf("%d", &n);
	for (int i = 0; i < n; i++)
	{
		printf("请输入第%d个元素的值:", i + 1);
		scanf("%d", &e);
		flag = EnQueue(Q, e);
		if (flag)printf("%d已入队\n", e);
		else { printf("队已满!!!\n"); break; }
	}
}
//出队函数 调用DeQueue函数
void DeleteFromQueue(SqQueue& Q)
{
	int n; QElemType e; int flag;
	printf("请输入出队元素个数(>=1):\n");
	scanf("%d", &n);
	for (int i = 0; i < n; i++)
	{
		flag = DeQueue(Q, e);
		if (flag)printf("%d已出队\n", e);
		else { printf("队已空!!!\n"); break; }
	}
}
//获得队头元素 调用GetHead函数
void GetHeadOfQueue(SqQueue Q)
{
	QElemType e; bool flag;
	flag = GetHead(Q, e);
	if (flag)printf("队头元素为:%d\n", e);
	else printf("队已空!!!\n");
}
//打印队内元素
void putQueue(SqQueue Q)
{
	cout << "当前队列为:";
	if (Q.front == Q.rear)cout << "队列为空";
	while (Q.front != Q.rear)
	{
		cout << Q.data[Q.front] << " ";
		Q.front = (Q.front + 1) % MaxQSize;
	}
	cout << endl;
}
//菜单
void menu()
{
	printf("********1.入队          2.出队*********\n");
	printf("********3.取队头元素    4.退出*********\n");
}
//主函数
int main()
{
	SqQueue Q; int choice;
	InitQueue(Q);
	while (1)
	{
		menu();
		printf("请输入菜单序号:\n");
		scanf("%d", &choice);
		if (choice == 4) break;
		switch (choice)
		{
		case 1:EnterToQueue(Q); break;
		case 2:DeleteFromQueue(Q); break;
		case 3:GetHeadOfQueue(Q); break;
		default:printf("输入错误!!!\n");
		}
	}
	putQueue(Q);
	return 0;
}

链队列的基本操作

/*
	InitQueue(LinkQueue &Q) 参数:链队Q 功能:初始化  时间复杂度O(1)
	EnQueue(LinkQueue &Q,QElemType e) 参数:链队Q,元素e 功能:将e入队 时间复杂度:O(1)
	DeQueue(LinkQueue &Q,QElemType &e) 参数:链队Q,元素e 功能:队头出队,e接收出队元素值 时间复杂度O(1)
	GetHead(LinkQueue &Q,QElemType &e)  参数:链队Q,元素e 功能:得到队顶元素 时间复杂度O(1)
	注意:有头结点
*/
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<queue>
#include<cmath>
#include<iostream>
using namespace std;
#define Status int
#define QElemType int
//链队结点数据结构
typedef struct QNode
{
	QElemType data;//数据域
	struct QNode* next;//指针域

}QNode, * QueuePtr;
typedef struct
{
	struct QNode* front, * rear;//rear指针指向队尾 用于入队 front指针指向队头 用于出队
}LinkQueue;
//**************************基本操作函数***************************//
//初始化函数
Status InitQueue(LinkQueue& Q)
{
	Q.front = Q.rear = new QNode;//生成新节点作为头结点,队头队尾指针均指向它
	Q.front->next = NULL;
	return 1;
}
//入队函数 
Status EnQueue(LinkQueue& Q, QElemType e)
{
	QNode* p;
	p = new QNode;//生成新节点
	p->data = e;    //赋值
	p->next = NULL;
	Q.rear->next = p;//加入队尾
	Q.rear = p;      //尾指针后移
	return 1;
}
//出队函数  队头出队用e返回 注意释放空间
bool DeQueue(LinkQueue& Q, QElemType& e)
{
	QueuePtr p;
	if (Q.front == Q.rear)return false;//队空
	e = Q.front->next->data;           //e返回值 之前写的Q.front->data 炸了,头结点没数据的,一定要注意头结点
	p = Q.front->next;                //保留,一会儿释放空间
	Q.front->next = p->next;          //出队,注意Q.front->next 不是Q.front 还有头结点
	if (Q.rear == p)Q.rear = Q.front;    //最后一个元素出队,rear指向头结点
	free(p);
	return true;
}
//取队顶函数 用e返回
bool GetHead(LinkQueue& Q, QElemType& e)
{
	if (Q.front == Q.rear) return false;//队列为空
	e = Q.front->next->data;
	return true;
}
//**************************功能实现函数***************************//
//菜单
void menu()
{
	printf("********1.入队          2.出队*********\n");
	printf("********3.取队顶元素    4.退出*********\n");
}
//入队功能函数 调用EnQueue函数 
void EnterToQueue(LinkQueue& Q)
{
	int n; QElemType e; int flag;
	printf("请输入入队元素个数(>=1):\n");
	scanf("%d", &n);
	for (int i = 0; i < n; i++)
	{
		printf("请输入第%d个元素的值:", i + 1);
		scanf("%d", &e);
		flag = EnQueue(Q, e);
		if (flag)printf("%d已入队\n", e);
	}
}
//出队功能函数 调用DeQueue函数
void DeleteFromQueue(LinkQueue& Q)
{
	int n; QElemType e; int flag;
	printf("请输入出队元素个数(>=1):\n");
	scanf("%d", &n);
	for (int i = 0; i < n; i++)
	{
		flag = DeQueue(Q, e);
		if (flag)printf("%d已出队\n", e);
		else { printf("队已空!!!\n"); break; }
	}
}
//取队顶功能函数 调用GetHead函数
void GetHeadOfStack(LinkQueue Q)
{
	QElemType e; bool flag;
	flag = GetHead(Q, e);
	if (flag)printf("队头元素为:%d\n", e);
	else printf("队已空!!!\n");
}

//打印链队队内元素
void Print(LinkQueue Q)
{
	QueuePtr p;
	if (Q.front == Q.rear)
	{
		cout << "队空" << endl;
		return;
	}
	while (Q.front != Q.rear)
	{
		cout << Q.front->next->data << " ";           //e返回值 之前写的Q.front->data 炸了,头结点没数据的,一定要注意头结点
		p = Q.front->next;                //保留,一会儿释放空间
		Q.front->next = p->next;          //出队,注意Q.front->next 不是Q.front 还有头结点
		if (Q.rear == p)Q.rear = Q.front;    //最后一个元素出队,rear指向头结点
		free(p);
	}
	cout << endl;
}
//主函数
int main()
{
	LinkQueue Q; int choice;
	InitQueue(Q);
	while (1)
	{
		menu();
		printf("请输入菜单序号:\n");
		scanf("%d", &choice);
		if (4 == choice) break;
		switch (choice)
		{
		case 1:EnterToQueue(Q); break;
		case 2:DeleteFromQueue(Q); break;
		case 3:GetHeadOfStack(Q); break;
		default:printf("输入错误!!!\n");
		}
	}
	Print(Q);
	return 0;
}

本章知识点小结

(1)顺序存储线性表特点:优点:可按存储位置随机访问,存储密度高。
缺点:插入和删除运算平均需移动大约表中一半的元素,存储空间是静态分配的,在程序执行期间必须明确规定存储规模,因此分配不足则会造成溢出,分配过大,又可能造成存储空间的浪费。
(2)链式存储线性表特点:优点:进行插入和删除操作时,只需要改变指针,不需要移动数据,不需要事先分配空间,便与表的扩充。
缺点:不能按序号随机访问第i个元素,存储密度降低。
(3)上溢:栈满时再做进栈运算产生的空间溢出。
(4)下溢:栈空时再做出栈运算产生的无元素可退。

笔记:
(1)链表中最常用的操作是在最后一个元素之后插入一个元素和删除最后一个元素,则采用 _仅有尾指针的单循环链表___存储方式最节省运算时间。
(2)若链表中最常用的操作是取第i个元素和找第i个元素的前驱元素,则采用 _顺序表___存储方式最节省运算时间。
(3)链表中最常用的操作是在最后一个元素之后插入一个元素和删除第一个元素,则采用 _仅有尾指针的单循环链表___存储方式最节省运算时间。
(4)递归过程或函数调用时,处理参数及返回地址,要用一种称为栈的数据结构。
(5)使用两个栈可以实现一个队列。
(6)使用一个数组可以实现多个队列。
(7)循环队列通常用队头、队尾下标来实现头尾相接。

第三章 线性结构的扩展

模式匹配

简单的模式匹配算法(BF算法)

/* 暴力模式匹配
int ViolentMatch(char* s, char* p)
{
	int sLen = strlen(s);
	int pLen = strlen(p);

	int i = 0;
	int j = 0;
	while (i < sLen && j < pLen)
	{
		if (s[i] == p[j])
		{
			//①如果当前字符匹配成功(即S[i] == P[j]),则i++,j++
			i++;
			j++;
		}
		else
		{
			//②如果失配(即S[i]! = P[j]),令i = i - (j - 1),j = 0
			i = i - j + 1;
			j = 0;
		}
	}
	//匹配成功,返回模式串p在文本串s中的位置,否则返回-1
	if (j == pLen)
		return i - j;
	else
		return -1;
}*/

改进后的模式匹配算法(KMP算法)

在这里插入图片描述

KMP算法

int KmpSearch(char* s, char* p)
{
	int i = 0;
	int j = 0;
	int sLen = strlen(s);
	int pLen = strlen(p);
	while (i < sLen && j < pLen)
	{
		//①如果j = -1,或者当前字符匹配成功(即S[i] == P[j]),都令i++,j++
		if (j == -1 || s[i] == p[j])
		{
			i++;
			j++;
		}
		else
		{
			//②如果j != -1,且当前字符匹配失败(即S[i] != P[j]),则令 i 不变,j = next[j]
			//next[j]即为j所对应的next值
			j = next[j];
		}
	}
	if (j == pLen)
		return i - j;
	else
		return -1;
}

KMP算法中求next数组

(1)next值的第一二位一定分别为0,1,后面求解每一位的next值时,根据前一位进行比较。
(2)从第三位开始,将前一位与其next值对应的内容进行比较,如果相等,则该位的next值就是前一位的next值加上1;
(3)如果不等,向前继续寻找next值对应的内容来与前一位进行比较,直到找到某个位上内容的next值对应的内容与前一位相等为止,则这个位对应的值加上1即为我们要求的next值;
(4)如果找到第一位都没有找到与前一位相等的内容,那么求解的位上的next值为1。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

求next数组代码

void GetNext(char* p,int next[])
{
	int pLen = strlen(p);
	next[0] = -1;
	int k = -1;
	int j = 0;
	while (j < pLen - 1)
	{
		//p[k]表示前缀,p[j]表示后缀
		if (k == -1 || p[j] == p[k])
		{
			++k;
			++j;
			next[j] = k;
		}
		else
		{
			k = next[k];
		}
	}
}

优化过后的next数组求法(nextval数组)

在这里插入图片描述

1.第一位的nextval值必定为0,第二位如果与第一位相同则为0,如果不同则为1。     
2.第三位的next值为1,那么将第三位和第一位进行比较,均为a,相同,则第三位的nextval值为0。 
3.第四位的next值为2,那么将第四位和第二位进行比较,不同,则第四位的nextval值为其next值,为2。 
4.第五位的next值为2,那么将第五位和第二位进行比较,相同,第二位的next值为1,则继续将第二位与第一位进行比较,不同,则第五位的nextval值为第二位的next值,为1。    
5.第六位的next值为3,那么将第六位和第三位进行比较,不同,则第六位的nextval值为其next值,为3。 
6.第七位的next值为1,那么将第七位和第一位进行比较,相同,则第七位的nextval值为0。 
7.第八位的next值为2,那么将第八位和第二位进行比较,不同,则第八位的nextval值为其next值,为2。

优化过后的next数组代码(nextval数组)

void GetNextval(char* p, int next[])
{
	int pLen = strlen(p);
	next[0] = -1;
	int k = -1;
	int j = 0;
	while (j < pLen - 1)
	{
		//p[k]表示前缀,p[j]表示后缀
		if (k == -1 || p[j] == p[k])
		{
			++j;
			++k;
			//较之前next数组求法,改动在下面4行
			if (p[j] != p[k])
				next[j] = k;   //之前只有这一行
			else
				//因为不能出现p[j] = p[ next[j ]],所以当出现时需要继续递归,k = next[k] = next[next[k]]
				next[j] = next[k];
		}
		else
		{
			k = next[k];
		}
	}
}*/

广义表

广义表的长度和深度

在这里插入图片描述

广义表的存储

头尾表示法

A =()
B =(e)
C =(a,(b,c,d))
D =(A,B,C)
E =(a,E)
F =(())
在这里插入图片描述

孩子兄弟表示法

A =()
B =(e)
C =(a,(b,c,d))
D =(A,B,C)
E =(a,E)
F =(())
在这里插入图片描述

第四章 树结构

树的特点

树具有下面两个特点:
⑴树的根结点没有前驱结点,除根结点之外的所有结点 有且只有一个前驱结点。
⑵树中所有结点可以有零个或多个后继结点。

树的相关术语

(1) 结点的度:结点所拥有的子树的个数
(2) 叶结点(终端节点):度为0的结点
(3) 分支结点(非终端结点):度不为0的结点
(4) 左孩子、右孩子、双亲、兄弟:树中一个结点的子树的根结点称为这个结点的孩子。在二叉树中,左子树的根称为左孩子,右子树的根称为右孩子。反过来这个结点称为它孩子结点的双亲。具有同一个双亲的孩子结点互称为兄弟
(5) 路径、路径长度
(6) 祖先、子孙
(7) 结点的层数:规定树的根结点的层数为1,其余结点的层数等于它的双亲结点的层数加1
(8) 树的深度:树中结点的最大层数
(9)树的度:树中各结点度的最大值
(10)有序树和无序树
(11)森林:任何一棵树,删去了根结点就变成了树

二叉树

二叉树的基本形态

在这里插入图片描述

满二叉树

如果一棵二叉树每一层的结点个数都达到了最大,这棵二叉树称为满二叉树。
在这里插入图片描述

完全二叉树

一棵深度为k的有n个结点的二叉树,对其结点按从上至下,从左到右的顺序编号,如果编号为i(1<=i<=n)的结点与满二叉树中编号为i的结点在二叉树中的位置相同,则称这棵二叉树为完全二叉树。
在这里插入图片描述
显然:一棵满二叉树必定是一棵完全二叉树,而完全二叉树未必是满二叉树。
笔记:对于任意一棵非空完全二叉树,若n为结点总数且n为奇数,则不存在度为1的结点。

二叉树的主要性质

性质1 一棵非空二叉树的第i层上最多有2^(i-1)个结点(i≥1)。
性质2 一棵深度为k的二叉树中,最多具有2^k-1个结点。
性质3 对于一棵非空的二叉树,若叶子结点数为n0,度数为2的结点数为n2,则有:n0=n2+1。
性质4 具有n个结点的完全二叉树的深度k为: 向下取整(log2n)+1。
性质5: 对于具有n个结点的完全二叉树,如果按照从上至下和从左到右的顺序对二叉树中的所有结点从1开始顺序编号,则对于任意的序号为i的结点,有:
⑴如果i>1,则序号为i的结点的双亲结点的序号为向下取整(i/2);如果i=1,则序号为i的结点是根结点,无双亲结点。
⑵如果2i≤n,则序号为i的结点的左孩子结点的序号为2i;如果2i>n,则序号为i的结点无左孩子。
⑶如果2i+1≤n,则序号为i的结点的右孩子结点的序号为2i+1;如果2i+1>n,则序号为i的结点无右孩子。
此外,若对二叉树的根结点从0开始编号,则相应的i号结点的双亲结点的编号为(i-1)/2,左孩子的编号为2i+1,右孩子的编号为2i+2。

二叉树的存储

顺序存储结构

所谓二叉树的顺序存储,是用一组连续的存储单元存放二叉树中的结点。一般是按照二叉树结点从上至下、从左到右的顺序存储。
在这里插入图片描述
对于一般的二叉树,如果仍按从上至下和从左到右的顺序将树中的结点顺序存储在一维数组中,则数组元素下标之间的关系不能够反映二叉树中结点之间的逻辑关系,只有增添一些并不存在的空结点,使之成为一棵完全二叉树的形式,然后再用一维数组顺序存储。
在这里插入图片描述
极端情况是单支树,如一棵深度为k的右单支树,只有k个结点,却需分配2k-1个存储单元。
在这里插入图片描述
显然,对于需增加许多空结点才能将一棵二叉树改造成为一棵完全二叉树的存储时,会造成空间的大量浪费,不宜用顺序存储结构。

链式存储结构
二叉链表存储

在这里插入图片描述
下图(a)给出一棵二叉树的二叉链表存储表示。二叉链表也可以带头结点的方式存放,如图(b)所示。
在这里插入图片描述

三叉链表存储

每个结点由四个域组成,具体结构为:
>
下图给出一棵二叉树的三叉链表存储示意图。
在这里插入图片描述

二叉树的遍历

递归方法实现二叉树的遍历

从二叉树的递归定义可知,一棵非空的二叉树由根结点及左、右子树这三个基本部分组成。因此,在任一给定结点上,可以按某种次序执行三个操作:
⑴访问结点本身(N),
⑵遍历该结点的左子树(L),
⑶遍历该结点的右子树(R)。
以上三种操作有六种执行次序:
NLR、LNR、LRN、NRL、RNL、RLN。
注意:前三种次序与后三种次序对称,故只讨论先左后右的前三种次序。
若以D、L、R分别表示访问根结点、遍历根结点的左子树、遍历根结点的右子树, 如果限定先左后右,则有三种方式,即:
DLR(称为先序遍历)
LDR(称为中序遍历)
LRD(称为后序遍历)

先序遍历

(1) 访问根结点;
(2) 先序遍历根结点的左子树;
(3) 先序遍历根结点的右子树。在这里插入图片描述
在这里插入图片描述

中序遍历

中序遍历的递归过程为:若二叉树为空,遍历结束。否则,
(1) 中序遍历根结点的左子树;
(2) 访问根结点;
(3) 中序遍历根结点的右子树。
在这里插入图片描述
在这里插入图片描述

后序遍历

后序遍历的递归过程为:若二叉树为空,遍历结束。否则,
(1) 后序遍历根结点的左子树;
(2) 后序遍历根结点的右子树;
(3) 访问根结点。
在这里插入图片描述
在这里插入图片描述

二叉树先序中序后序层序遍历代码:

/*
InitTree(BiTree &T) 参数T,二叉树根节点 作用:初始化二叉树,先序递归创建
PreOrder(BiTree T)  参数T,二叉树根节点 作用:先序遍历二叉树,递归方式
InOrder(BiTree T)   参数T,二叉树根节点 作用:中序遍历二叉树,递归方式
PostOrder(BiTree T) 参数T,二叉树根节点 作用:后序遍历二叉树,递归方式
LevelOrder(BiTree T)参数T,二叉树根节点 作用:层序遍历二叉树,递归方式
CreateBiTree(BiTree &T) 参数T,二叉树根节点 作用:调用InitTree,创建二叉树
Traverse(BiTree T)      参数T,二叉树根节点 作用:PreOrder InOrder PostOrder LevelOrder遍历二叉树
*/
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<string>
#include<stack>
#include<queue>
#include<algorithm>
#include<iostream>
#define TElemType int
using namespace std;
//链式二叉树数据结构
typedef struct BiTNode
{
	TElemType data;//数据域
	struct BiTNode* lchild, * rchild;//左右孩子
}BiTNode, * BiTree;
//********************************基本操作函数********************************//
//创建二叉树 规定数据域为-1,则为空 先序创建
int InitTree(BiTree& T)
{
	TElemType a;
	scanf("%d", &a);
	if (-1 == a) T = NULL;
	else {
		T = (BiTree)malloc(sizeof(BiTNode));
		T->data = a;
		InitTree(T->lchild);
		InitTree(T->rchild);
	}
	return 0;
}
//先序遍历-递归
void PreOrder(BiTree T)
{
	if (T != NULL)
	{
		printf("%d ", T->data);
		PreOrder(T->lchild);//递归先序遍历左右子树
		PreOrder(T->rchild);
	}
}
//中序遍历-递归
void InOrder(BiTree T)
{
	if (T != NULL)
	{
		InOrder(T->lchild);//递归中序遍历左右子树
		printf("%d ", T->data);
		InOrder(T->rchild);
	}
}
//后序遍历-递归
void PostOrder(BiTree T)
{
	if (T != NULL)
	{
		PostOrder(T->lchild);//递归后序遍历左右子树
		PostOrder(T->rchild);
		printf("%d ", T->data);
	}
}
//层序遍历
void LevelOrder(BiTree T)
{
	queue<BiTNode> q;//借助队列
	if (T != NULL)
	{
		BiTNode temp;//暂存要出队的结点
		q.push(*T);//根结点入队
		while (!q.empty())//队列非空
		{
			temp = q.front();
			q.pop();
			printf("%d ", temp.data);
			if (temp.lchild != NULL) q.push(*temp.lchild);//队列先进先出,先入左孩子
			if (temp.rchild != NULL) q.push(*temp.rchild);
		}
	}
}
//**********************************功能实现函数*****************************//
//调用InitTree
void CreateBiTree(BiTree& T)
{
	printf("请按照先序遍历输入二叉树(-1无):");
	InitTree(T);
	printf("二叉树先序遍历序列:");
	PreOrder(T);
	printf("\n");
}
//遍历功能函数 调用PreOrder InOrder PostOrder LevelOrder
void Traverse(BiTree T)
{
	int choice;
	while (1)
	{
		printf("********1.先序遍历    2.中序遍历*********\n");
		printf("********3.后序遍历    4.层次遍历*********\n");
		printf("********5.返回上一单元\n");
		printf("请输入菜单序号:\n");
		scanf("%d", &choice);
		if (5 == choice) break;
		switch (choice)
		{
		case 1: {printf("二叉树先序遍历序列:"); PreOrder(T); printf("\n"); }break;
		case 2: {printf("二叉树中序遍历序列:"); InOrder(T); printf("\n"); }break;
		case 3: {printf("二叉树后序遍历序列:"); PostOrder(T); printf("\n"); }break;
		case 4: {printf("二叉树层次遍历序列:"); LevelOrder(T); printf("\n"); }break;
		default:printf("输入错误!!!\n"); break;
		}
	}
}
//菜单
void menu()
{
	printf("********1.创建    2.遍历*********\n");
	printf("********3.退出\n");
}
//主函数
int main()
{
	BiTree T = NULL; int choice = 0;
	while (1)
	{
		menu();
		printf("请输入菜单序号:\n");

		scanf("%d", &choice);
		if (3 == choice) break;
		switch (choice)
		{
		case 1:CreateBiTree(T); break;
		case 2:Traverse(T); break;
		default:printf("输入错误!!!\n"); break;
		}
	}
	return 0;
}

非递归方法实现二叉树的遍历

在这里插入图片描述

由遍历序列恢复二叉树

依据遍历定义:
由二叉树的先序序列和中序序列可唯一地确定该二叉树。
由二叉树的后序序列和中序序列也可唯一地确定该二叉树。
由二叉树的先序序列和后序序列不能唯一地确定该二叉树。

#include<iostream>
#include<string>
using namespace std;
/*
假设根节点在中序遍历中的位置为pos,树的结点数为len,即 len=inorder.length()
代码:pos = inorder.find(preorder[0]) 
先序遍历(NLR), 根节点编号(0), 左子树编号(1~pos), 右子树编号(pos+1~len-1)
中序遍历(LNR), 左子树编号(0~pos-1), 根节点编号(pos), 右子树编号(pos+1~len-1)
后序遍历(LRN), 左子树编号(0~pos-1), 右子树编号(pos~len-2), 根点编号(len-1)
*/
void postorder(string preorder, string inorder) {//由先序遍历+中序遍历序列,递归实现后序遍历 (LRN) 
	int len = preorder.length();
	if (len == 0)
		return;
	if (len == 1)
	{  //单个结点 
		cout << preorder[0];
		return;
	}
	int pos = inorder.find(preorder[0]);   // 查找根节点在中序序列中的位置,通过根节点划分左右子树 
	// 类似于后序遍历过程
	postorder(preorder.substr(1, pos), inorder.substr(0, pos));//后序遍历左子树
	postorder(preorder.substr(pos + 1, len - pos - 1), inorder.substr(pos + 1, len - pos - 1));//后序遍历右子树,pos从0开始,所以len-pos-1 
	cout << preorder[0];    //最后输出根节点 
}
void preorder(string inorder, string postorder) //由中序遍历+后序遍历序列,递归实现先序序列 (NLR)
{
	int len = postorder.length();
	if (len == 0) // 空树 
		return;
	if (len == 1)   // 单个结点
	{
		cout << inorder[0];
		return;
	}
	int pos = inorder.find(postorder[len - 1]);
	// 类似于先序遍历过程
	cout << postorder[len - 1];
	preorder(inorder.substr(0, pos), postorder.substr(0, pos)); //先序遍历左子树 
	preorder(inorder.substr(pos + 1, len - pos - 1), postorder.substr(pos, len - pos - 1));//先序遍历右子树 
}

int main()
{
	string s1, s2;
	while (1)
	{
		printf("输入先序序列和中序序列:\n");
		cin >> s1 >> s2;
		cout << "由先序遍历+中序遍历序列递归实现后序遍历 (LRN):" << endl;
		postorder(s1, s2);
		cout << endl;
		printf("输入中序序列和后序序列:\n");
		cin >> s1 >> s2;
		cout << "由中序遍历+后序遍历序列递归实现先序遍历 (NRL):" << endl;
		preorder(s1, s2); 
		cout << endl;
	}
}

线索二叉树

线索二叉树的定义及结构

1.线索二叉树的定义:
利用二叉链表中左、右空指针建立起指向前驱后继的指针,称之为线索。加了线索的二叉树称为线索二叉树。
一个具有n个结点的二叉树若采用二叉链表存储,则共有2n个指针域,其中n-1个指针域用来存储孩子结点的地址,n+1个指针域存放的都是空指针,因此,可以利用这些空指针来存放线索。

2.线索二叉树的结构
图中实线表示指针,虚线表示线索
在这里插入图片描述

线索二叉树的遍历

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

最优二叉树

几个基本概念:

1.结点的权
2.带权路径长度
设二叉树具有n个带权值的叶结点,那么从根结点到各个叶结点的路径长度与相应结点权值的乘积之和叫做二叉树的带权路径长度。记为:
WPL=求和(Wk*Lk)(1<=k<=n)
其中Wk为第k个叶结点的权值,Lk为第k个叶结点的路径长度 。 3.哈夫曼(Haffman)树:
哈夫曼树也称最优二叉树。由相同权值的一组叶子结点所构成的二叉树可能有不同的形态和不同的带权路径长度,具有最小带权路径长度的二叉树称为哈夫曼(Haffman)树,也称最优二叉树。无度为1的结点,n个叶结点的最优二叉树结点总数为2n-1。

最优二叉树的构造

1、构造方法
哈夫曼(Haffman)依据这一特点提出了一种方法,这种方法的基本思想是:
(1)由给定的n个权值{W1,W2,…,Wn}构造n棵只有一个叶结点的二叉树,从而得到一个二叉树的集合F={T1,T2,…,Tn};
(2)在F中选取根结点的权值最小和次小的两棵二叉树作为左、右子树构造一棵新的二叉树,这棵新的二叉树根结点的权值为其左、右子树根结点权值之和;
(3)在集合F中删除作为左、右子树的两棵二叉树,并将新建立的二叉树加入到集合F中;
(4)重复(2)(3)两步,当F中只剩下一棵二叉树时,这棵二叉树便是所要建立的哈夫曼树。
在这里插入图片描述

#include <cstdio>
#include <cstring>
#include<iostream>
using namespace std;

typedef struct HTNode
{
    int weight;         // 结点权值
    int parent, lc, rc; // 双亲结点和左右孩子节点
} HTNode, * HuffmanTree;

void Select(HuffmanTree& HT, int n, int &s1, int &s2)//寻找两个权值最小的结点
{
    int minum;      // 定义一个临时变量保存最小值
    for (int i = 1; i <= n; i++)     // 以下是找到第一个最小值
    {
        if (HT[i].parent == 0)
        {
            minum = i;
            break;
        }
    }
    for (int i = 1; i <= n; i++)
    {
        if (HT[i].parent == 0)
            if (HT[i].weight < HT[minum].weight)
                minum = i;
    }
    s1 = minum;
    // 以下是找到第二个最小值,且与第一个不同
    for (int i = 1; i <= n; i++)
    {
        if (HT[i].parent == 0 && i != s1)
        {
            minum = i;
            break;
        }
    }
    for (int i = 1; i <= n; i++)
    {
        if (HT[i].parent == 0 && i != s1)
            if (HT[i].weight < HT[minum].weight)
                minum = i;
    }
    s2 = minum;
}

void CreatHuff(HuffmanTree& HT, int* w, int n)//创建哈夫曼树
{
    int m, s1, s2;
    m = n * 2 - 1;  // 总结点的个数
    HT = new HTNode[m + 1]; // 分配空间
    for (int i = 1; i <= n; i++) // 1 - n 存放叶子结点,初始化
    {
        HT[i].weight = w[i];
        HT[i].parent = 0;
        HT[i].lc = 0;
        HT[i].rc = 0;
    }
    for (int i = n + 1; i <= m; i++)   // 非叶子结点的初始化
    {
        HT[i].weight = 0;
        HT[i].parent = 0;
        HT[i].lc = 0;
        HT[i].rc = 0;
    }

    printf("\nthe HuffmanTree is: \n");

    for (int i = n + 1; i <= m; i++)     // 创建非叶子节点,建哈夫曼树
    {   // 在HT[1]~HT[i-1]的范围内选择两个parent为0且weight最小的两个结点,其序号分别赋值给 s1 s2
        Select(HT, i - 1, s1, s2);
        HT[s1].parent = i;  // 删除这两个结点
        HT[s2].parent = i;
        HT[i].lc = s1;      // 生成新的树,左右子节点是 s1和s2
        HT[i].rc = s2;
        HT[i].weight = HT[s1].weight + HT[s2].weight;   // 新树的权
        printf("%d (%d, %d)\n", HT[i].weight, HT[s1].weight, HT[s2].weight);
    }
    printf("\n");
}

int main()
{
    HuffmanTree HT;
    int *w, n, wei;
    printf("input the number of node\n");
    scanf("%d", &n);
    w = new int[n + 1];
    printf("\ninput the %dth node of value\n", n);
    for (int i = 1; i <= n; i++)
    {
        scanf("%d", &wei);
        w[i] = wei;
    }
    CreatHuff(HT, w, n);
    return 0;
}
哈夫曼编码

利用哈夫曼树可以对字符编出效率最高的编码,称为的哈夫曼码。
1、编码方法
(1) 设需要编码的字符集合为{d1,d2,…,dn},它们在电文中出现的次数或频率集合为{w1,w2,…,wn},以d1,d2,…,dn作为叶结点,w1,w2,…,wn作为它们的权值,构造一棵哈夫曼树。
(2) 规定哈夫曼树中的左分支代表0,右分支代表1,则从根结点到每个叶结点所经过的路径分支组成的0和1的序列便为该结点对应字符的编码,我们称之为哈夫曼编码。
2、前缀码:给定一个集合的序列,若没有一个序列是另一个序列的前缀,则称该序列集合为前缀码。
3、最优前缀码:平均码长最小。

树和森林

树的存储

两个指针域分别指向该结点的第一个孩子和下一个兄弟结点
在这里插入图片描述

树和森林与二叉树之间的转换

树转换成二叉树

转换的方法依据树的孩子-兄弟存储方法,将一棵树转换为二叉树的方法是:
(1) 树中每个结点的第一个孩子留作该结点的左孩子;
(2) 从结点的第二个孩子起,将作为原左兄弟的右孩子。
(3) 删去结点与其它孩子结点(除第一个孩子外)之间的连线。
在这里插入图片描述

森林转换为二叉树

森林转换为二叉树的方法如下:
(1) 将森林中的每棵树转换成相应的二叉树。
(2) 第一棵二叉树不动,从第二棵二叉树开始,依次把后一棵二叉树的根结点作为前一棵二叉树根结点的右孩子,当所有二叉树连起来后,此时所得到的二叉树就是由森林转换得到的二叉树。
在这里插入图片描述

二叉树转换成树和森林

树和森林转换为二叉树的过程是可逆的,具体方法如下:
⑴若某结点是其双亲的左孩子,则把该结点的右孩子、右孩子的右孩子……都与该结点的双亲结点用线连起来;
⑵删去原二叉树中所有的双亲结点与右孩子结点的连线;
⑶整理由⑴、⑵两步所得到的树或森林,使之结构层次分明。
在这里插入图片描述

第五章 图结构

相关概念

(1)无向图
(2)有向图
(3) 邻接点、邻接边
(4) 完全图
(5) 稠密图、稀疏图
(6) 顶点的度、入度、出度
(7) 边的权、网图
(8) 路径和路径长度
(9) 回路、简单路径、简单回路
(10) 子图
(11) 连通的、连通图、连通分量
(12) 强连通图、强连通分量
(13) 连通图(或子图)的生成树
(14) 非连通图的生成森林

图的存储方法

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

图的遍历

概念

图的遍历是指从图中的任一顶点出发,对图中的所有顶点访问一次且只访问一次。图的遍历是图的一种基本操作,图的许多其它操作都是建立在遍历操作的基础之上。
图的遍历操作较为复杂,主要表现在:
(1)在图结构中,没有一个“自然”的首结点,图中任意一个顶点都可作为第一个被访问的结点。
(2)在非连通图中,从一个顶点出发,只能访问它所在的连通分量上的所有顶点,因此,还需考虑如何选取下一个出发点以访问图中其余的连通分量。
(3)考虑回路问题。
(4)如何选取下一个要访问的顶点的问题。

深度优先搜索

深度优先搜索(Depth_Fisrst Search)遍历类似于树的先根遍历,是树的先根遍历的推广。
1、深度优先搜索的概念
假设初始状态是图中所有顶点均未被访问,则从某个顶点v出发,首先访问该顶点,然后依次从它的各个未被访问的邻接点出发深度优先搜索遍历图,直至图中所有和v有路径相通的顶点都被访问到。
若此时尚有其他顶点未被访问到,则另选一个未被访问的顶点作起始点,重复上述过程,直至图中所有顶点都被访问到为止。
在这里插入图片描述

广度优先搜索

广度优先搜索(Breadth_First Search) 遍历类似于树的按层次遍历的过程。
1、广度优先搜索的概念
从图中某顶点v出发,在访问了v之后依次访问v的各个未曾访问过的邻接点,然后分别从这些邻接点出发依次访问它们的邻接点,并使得“先被访问的顶点的邻接点先于后被访问的顶点的邻接点被访问”,直至图中所有已被访问的顶点的邻接点都被访问到。
如果此时图中尚有顶点未被访问,则需要另选一个未曾被访问过的顶点作为新的起始点,重复上述过程,直至图中所有顶点都被访问到为止。
在这里插入图片描述

综合代码:

/*
基本操作函数:
InitGraph(Graph &G)             初始化函数 参数:图G 作用:初始化图的顶点表,邻接矩阵等
InsertNode(Graph &G,VexType v) 插入点函数 参数:图G,顶点v 作用:在图G中插入顶点v,即改变顶点表
InsertEdge(Graph &G,VexType v,VexType w) 插入边函数 参数:图G,某边两端点v和w 作用:在图G两点v,w之间加入边,即改变邻接矩阵
Adjancent(Graph G,VexType v,VexType w) 判断是否存在边(v,w)函数 参数:图G,某边两端点v和w 作用:判断是否存在边(v,w)
BFS(Graph G, int start) 广度遍历函数 参数:图G,开始结点下标start 作用:宽度遍历
DFS(Graph G, int start) 深度遍历函数(递归形式)参数:图G,开始结点下标start 作用:深度遍历
功能实现函数:
CreateGraph(Graph &G) 创建图功能实现函数 参数:图G  InsertNode 作用:创建图
BFSTraverse(Graph G)  广度遍历功能实现函数 参数:图G 作用:宽度遍历
DFSTraverse(Graph G)  深度遍历功能实现函数 参数:图G 作用:深度遍历
*/
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<string>
#include<set>
#include<list>
#include<queue>
#include<vector>
#include<map>
#include<iterator>
#include<algorithm>
#include<iostream>
#define MaxVerNum 100 //顶点最大数目值
#define VexType char //顶点数据类型
#define EdgeType int //边数据类型,无向图时邻接矩阵对称,有权值时表示权值,没有时1连0不连
using namespace std;
//图的数据结构
typedef struct Graph
{
	VexType Vex[MaxVerNum];//顶点表
	EdgeType Edge[MaxVerNum][MaxVerNum];//边表
	int vexnum, arcnum;//顶点数、边数
}Graph;
//*********************************************基本操作函数*****************************************//
//初始化函数 参数:图G 作用:初始化图的顶点表,邻接矩阵等
void InitGraph(Graph& G)
{
	memset(G.Vex, '#', sizeof(G.Vex));//初始化顶点表
	memset(G.Edge, 0, sizeof(G.Edge));//初始化边表
	G.vexnum = G.arcnum = 0;          //初始化顶点数、边数
}
//插入点函数 参数:图G,顶点v 作用:在图G中插入顶点v,即改变顶点表
bool InsertNode(Graph& G, VexType v)
{
	if (G.vexnum < MaxVerNum)
	{
		G.Vex[G.vexnum++] = v;
		return true;
	}
	return false;
}
//插入边函数 参数:图G,某边两端点v和w 作用:在图G两点v,w之间加入边,即改变邻接矩阵
bool InsertEdge(Graph& G, VexType v, VexType w)
{
	int p1, p2;//v,w两点下标
	p1 = p2 = -1;//初始化
	for (int i = 0; i < G.vexnum; i++)//寻找顶点下标
	{
		if (G.Vex[i] == v)p1 = i;
		if (G.Vex[i] == w)p2 = i;
	}
	if (-1 != p1 && -1 != p2)//两点均可在图中找到
	{
		G.Edge[p1][p2] = G.Edge[p2][p1] = 1;//无向图邻接矩阵对称
		G.arcnum++;//边的数目加1
		return true;
	}
	return false;
}
//判断是否存在边(v,w)函数参数:图G,某边两端点v和w 作用:判断是否存在边(v,w) 
bool Adjancent(Graph G, VexType v, VexType w)
{
	int p1, p2;//v,w两点下标
	p1 = p2 = -1;//初始化
	for (int i = 0; i < G.vexnum; i++)//寻找顶点下标
	{
		if (G.Vex[i] == v)p1 = i;
		if (G.Vex[i] == w)p2 = i;
	}
	if (-1 != p1 && -1 != p2)//两点均可在图中找到
	{
		if (G.Edge[p1][p2] == 1)//存在边
		{
			return true;
		}
		return false;
	}
	return false;
}
bool visited[MaxVerNum];//访问标记数组,用于遍历时的标记

//广度遍历函数 参数:图G,开始结点下标start 作用:宽度遍历
void BFS(Graph G, int start)
{
	queue<int> Q;//辅助队列
	cout << G.Vex[start];//访问结点
	visited[start] = true;
	Q.push(start);//入队
	while (!Q.empty())//队列非空
	{
		int v = Q.front();//得到队头元素
		Q.pop();//出队
		for (int j = 0; j < G.vexnum; j++)//访问邻接点
		{
			if (G.Edge[v][j] == 1 && !visited[j])//是邻接点且未访问
			{
				cout << "->";
				cout << G.Vex[j];//访问结点
				visited[j] = true;
				Q.push(j);//入队
			}
		}
	}
	cout << endl;
}
//深度遍历函数(递归形式)参数:图G,开始结点下标start 作用:深度遍历
void DFS(Graph G, int start)
{
	cout << G.Vex[start];//访问
	visited[start] = true;
	for (int j = 0; j < G.vexnum; j++)
	{
		if (G.Edge[start][j] == 1 && !visited[j])//是邻接点且未访问
		{
			cout << "->";
			DFS(G, j);//递归深度遍历
		}
	}
}
//**********************************************功能实现函数*****************************************//
//打印图的顶点表
void PrintVex(Graph G)
{
	for (int i = 0; i < G.vexnum; i++)
	{
		cout << G.Vex[i] << " ";
	}
	cout << endl;
}
//打印图的边矩阵
void PrintEdge(Graph G)
{
	for (int i = 0; i < G.vexnum; i++)
	{
		for (int j = 0; j < G.vexnum; j++)
		{
			cout << G.Edge[i][j] << " ";
		}
		cout << endl;
	}
}
//创建图功能实现函数 参数:图G  InsertNode 作用:创建图
void CreateGraph(Graph& G)
{
	VexType v, w;
	int vn, an;//顶点数,边数
	cout << "请输入顶点数目:" << endl;
	cin >> vn;
	cout << "请输入边数目:" << endl;
	cin >> an;
	cout << "请输入所有顶点名称:" << endl;
	for (int i = 0; i < vn; i++)
	{
		cin >> v;
		if (InsertNode(G, v)) 
			continue;//插入点
		else 
		{
			cout << "输入错误!" << endl; 
			break;
		}
	}
	cout << "请输入所有边(每行输入边连接的两个顶点):" << endl;
	for (int j = 0; j < an; j++)
	{
		cin >> v >> w;
		if (InsertEdge(G, v, w))
			continue;//插入边
		else 
		{
			cout << "输入错误!" << endl; break;
		}
	}
	PrintVex(G);
	PrintEdge(G);
}
//广度遍历功能实现函数 参数:图G 作用:宽度遍历
void BFSTraverse(Graph G)
{
	for (int i = 0; i < MaxVerNum; i++)//初始化访问标记数组
	{
		visited[i] = false;
	}
	for (int i = 0; i < G.vexnum; i++)//对每个连通分量进行遍历
	{
		if (!visited[i])
			BFS(G, i);
	}
}
//深度遍历功能实现函数 参数:图G 作用:深度遍历
void DFSTraverse(Graph G)
{
	for (int i = 0; i < MaxVerNum; i++)//初始化访问标记数组
	{
		visited[i] = false;
	}
	for (int i = 0; i < G.vexnum; i++)//对每个连通分量进行遍历
	{
		if (!visited[i])
		{
			DFS(G, i);
			cout << endl;
		}
	}
}
//菜单
void menu()
{
	cout << "************1.创建图       2.广度遍历************" << endl;
	cout << "************3.深度遍历     4.退出****************" << endl;
}
//主函数
int main()
{
	int choice = 0;
	Graph G;
	InitGraph(G);
	while (1)
	{
		menu();
		printf("请输入菜单序号:\n");
		scanf("%d", &choice);
		if (4 == choice) break;
		switch (choice)
		{
		case 1:CreateGraph(G); break;
		case 2:BFSTraverse(G); break;
		case 3:DFSTraverse(G); break;
		default:printf("输入错误!!!\n"); break;
		}
	}
	return 0;
}

生成树和生成森林

生成树和生成森林的概念

对于无向连通图,在图的深度优先遍历或广度优先搜索遍历过程中经历的边的集合和图中的所有顶点一起构成图的极小连通子图,就是一颗生成树(深度优先生成树、广度优先生成树)。
对非连通无向图,深度优先搜索遍历或广度优先搜索遍历,每个连通分量中的顶点集合遍历时走过的边一起构成若干颗生成树,这些连通分量的生成树组成非连通图的生成森林(深度优先生成森林、广度优先生成森林)

最小生成树

概念

如果无向连通图是一个带权图,那么,它的所有生成树中必有一棵边的权值总和最小的生成树,我们称这棵生成树为最小代价生成树,简称为最小生成树。

Prim算法

在这里插入图片描述

/*
基本操作函数:
InitGraph(Graph &G)             初始化函数 参数:图G 作用:初始化图的顶点表,邻接矩阵等
InsertNode(Graph &G,VexType v) 插入点函数 参数:图G,顶点v 作用:在图G中插入顶点v,即改变顶点表
InsertEdge(Graph &G,VexType v,VexType w) 插入边函数 参数:图G,某边两端点v和w 作用:在图G两点v,w之间加入边,即改变邻接矩阵
Adjancent(Graph G,VexType v,VexType w) 判断是否存在边(v,w)函数 参数:图G,某边两端点v和w 作用:判断是否存在边(v,w)
BFS(Graph G, int start) 广度遍历函数 参数:图G,开始结点下标start 作用:宽度遍历
DFS(Graph G, int start) 深度遍历函数(递归形式)参数:图G,开始结点下标start 作用:深度遍历
CreateGraph(Graph &G) 创建图功能实现函数 参数:图G  InsertNode 作用:创建图
BFSTraverse(Graph G)  广度遍历功能实现函数 参数:图G 作用:宽度遍历
DFSTraverse(Graph G)  深度遍历功能实现函数 参数:图G 作用:深度遍历
Prim(Graph G) 最小生成树-Prim算法 参数:图G
*/
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<string>
#include<set>
#include<list>
#include<queue>
#include<vector>
#include<map>
#include<iterator>
#include<algorithm>
#include<iostream>
#define MaxVerNum 100 //顶点最大数目值
#define VexType char //顶点数据类型
#define EdgeType int //边数据类型,无向图时邻接矩阵对称,有权值时表示权值,没有时1连0不连
#define INF 0x3f3f3f3f//作为最大值
using namespace std;
//图的数据结构
typedef struct Graph
{
	VexType Vex[MaxVerNum];//顶点表
	EdgeType Edge[MaxVerNum][MaxVerNum];//边表
	int vexnum, arcnum;//顶点数、边数
}Graph;

//Prim算法所用数据结构
typedef struct closedge
{
	int adjvex;     //最小边在当前子树顶点集合的那个顶点的下标
	int lowcost;    //最小边上的权值
};

//*********************************************基本操作函数*****************************************//
//初始化函数 参数:图G 作用:初始化图的顶点表,邻接矩阵等
void InitGraph(Graph& G)
{
	memset(G.Vex, '#', sizeof(G.Vex));//初始化顶点表
	//初始化边表
	for (int i = 0; i < MaxVerNum; i++)
		for (int j = 0; j < MaxVerNum; j++)
		{
			G.Edge[i][j] = INF;
			if (i == j)
			G.Edge[i][j] = 0;//在最小生成树时,考虑无环简单图,故自己到自己设置为0
		}
	G.arcnum = G.vexnum = 0;          //初始化顶点数、边数
}
//插入点函数 参数:图G,顶点v 作用:在图G中插入顶点v,即改变顶点表
bool InsertNode(Graph& G, VexType v)
{
	if (G.vexnum < MaxVerNum)
	{
		G.Vex[G.vexnum++] = v;
		return true;
	}
	return false;
}
//插入边函数 参数:图G,某边两端点v和w 作用:在图G两点v,w之间加入边,即改变邻接矩阵
bool InsertEdge(Graph& G, VexType v, VexType w, int weight)
{
	int p1, p2;//v,w两点下标
	p1 = p2 = -1;//初始化
	for (int i = 0; i < G.vexnum; i++)//寻找顶点下标
	{
		if (G.Vex[i] == v)p1 = i;
		if (G.Vex[i] == w)p2 = i;
	}
	if (-1 != p1 && -1 != p2)//两点均可在图中找到
	{
		G.Edge[p1][p2] = G.Edge[p2][p1] = weight;//无向图邻接矩阵对称
		G.arcnum++;
		return true;
	}
	return false;
}
//判断是否存在边(v,w)函数 参数:图G,某边两端点v和w 作用:判断是否存在边(v,w) 
bool Adjancent(Graph G, VexType v, VexType w)
{
	int p1, p2;//v,w两点下标
	p1 = p2 = -1;//初始化
	for (int i = 0; i < G.vexnum; i++)//寻找顶点下标
	{
		if (G.Vex[i] == v)p1 = i;
		if (G.Vex[i] == w)p2 = i;
	}
	if (-1 != p1 && -1 != p2)//两点均可在图中找到
	{
		if (G.Edge[p1][p2] == 1)//存在边
		{
			return true;
		}
		return false;
	}
	return false;
}

bool visited[MaxVerNum];//访问标记数组,用于遍历时的标记
//广度遍历函数 参数:图G,开始结点下标start 作用:宽度遍历

void BFS(Graph G, int start)
{
	queue<int> Q;//辅助队列
	cout << G.Vex[start];//访问结点
	visited[start] = true;
	Q.push(start);//入队
	while (!Q.empty())//队列非空
	{
		int v = Q.front();//得到队头元素
		Q.pop();//出队
		for (int j = 0; j < G.vexnum; j++)//邻接点
		{
			if (G.Edge[v][j] < INF && !visited[j])//是邻接点且未访问
			{
				cout << "->";
				cout << G.Vex[j];//访问结点
				visited[j] = true;
				Q.push(j);//入队
			}
		}
	}
	cout << endl;
}
//深度遍历函数(递归形式)参数:图G,开始结点下标start 作用:深度遍历
void DFS(Graph G, int start)
{
	cout << G.Vex[start];//访问
	visited[start] = true;
	for (int j = 0; j < G.vexnum; j++)
	{
		if (G.Edge[start][j] < INF && !visited[j])//是邻接点且未访问
		{
			cout << "->";
			DFS(G, j);//递归深度遍历
		}
	}
}

//**********************************************功能实现函数*****************************************//
//打印图的顶点表
void PrintVex(Graph G)
{
	for (int i = 0; i < G.vexnum; i++)
	{
		cout << G.Vex[i] << " ";
	}
	cout << endl;
}
//打印图的边矩阵
void PrintEdge(Graph G)
{
	for (int i = 0; i < G.vexnum; i++)
	{
		for (int j = 0; j < G.vexnum; j++)
		{
			if (G.Edge[i][j] == INF)cout << "∞ ";
			else cout << G.Edge[i][j] << " ";
		}
		cout << endl;
	}
}
//创建图功能实现函数 参数:图G  InsertNode 作用:创建图
void CreateGraph(Graph& G)
{
	VexType v, w;
	int vn, an;//顶点数,边数
	cout << "请输入顶点数目:" << endl;
	cin >> vn;
	cout << "请输入边数目:" << endl;
	cin >> an;
	cout << "请输入所有顶点名称:" << endl;
	for (int i = 0; i < vn; i++)
	{
		cin >> v;
		if (InsertNode(G, v)) continue;//插入点
		else {
			cout << "输入错误!" << endl; break;
		}
	}
	cout << "请输入所有边(每行输入边连接的两个顶点及权值):" << endl;
	for (int j = 0; j < an; j++)
	{
		int weight;
		cin >> v >> w >> weight;
		if (InsertEdge(G, v, w, weight)) continue;//插入边
		else {
			cout << "输入错误!" << endl; break;
		}
	}
	PrintVex(G);
	PrintEdge(G);
}
//广度遍历功能实现函数 参数:图G 作用:宽度遍历
void BFSTraverse(Graph G)
{
	for (int i = 0; i < MaxVerNum; i++)//初始化访问标记数组
	{
		visited[i] = false;
	}
	for (int i = 0; i < G.vexnum; i++)//对每个连通分量进行遍历
	{
		if (!visited[i])BFS(G, i);
	}
}
//深度遍历功能实现函数 参数:图G 作用:深度遍历
void DFSTraverse(Graph G)
{
	for (int i = 0; i < MaxVerNum; i++)//初始化访问标记数组
	{
		visited[i] = false;
	}
	for (int i = 0; i < G.vexnum; i++)//对每个连通分量进行遍历
	{
		if (!visited[i])
		{
			DFS(G, i); cout << endl;
		}
	}
}

//最小生成树-Prim算法 参数:图G
void Prim(Graph G)
{
	int v = 0;//初始节点
	closedge C[MaxVerNum];
	int mincost = 0; //记录最小生成树的各边权值之和
	//初始化
	for (int i = 0; i < G.vexnum; i++)
	{
		C[i].adjvex = v;
		C[i].lowcost = G.Edge[v][i];
	}
	cout << "最小生成树的所有边:" << endl;
	//初始化完毕,开始G.vexnum-1次循环
	for (int i = 1; i < G.vexnum; i++)
	{
		int k;
		int min = INF;
		//求出与集合U权值最小的点 权值为0的代表在集合U中
		for (int j = 0; j < G.vexnum; j++)
		{
			if (C[j].lowcost != 0 && C[j].lowcost < min)
			{
				min = C[j].lowcost;
				k = j;
			}
		}
		//输出选择的边并累计权值
		cout << "(" << G.Vex[k] << "," << G.Vex[C[k].adjvex] << ") ";
		mincost += C[k].lowcost;
		//更新最小边
		for (int j = 0; j < G.vexnum; j++)
		{
			if (C[j].lowcost != 0 && G.Edge[k][j] < C[j].lowcost)
			{
				C[j].adjvex = k;
				C[j].lowcost = G.Edge[k][j];
			}
		}
	}
	cout << "最小生成树权值之和:" << mincost << endl;
}
//菜单
void menu()
{
	cout << "************1.创建图           2.广度遍历******************" << endl;
	cout << "************3.深度遍历         4.最小生成树(Prim)**********" << endl;
	cout << "************5.  退出   ****************" << endl;
}
//主函数
int main()
{
	int choice = 0;
	Graph G;
	InitGraph(G);
	while (1)
	{
		menu();
		printf("请输入菜单序号:\n");
		scanf("%d", &choice);
		if (5== choice) break;
		switch (choice)
		{
		case 1:CreateGraph(G); break;
		case 2:BFSTraverse(G); break;
		case 3:DFSTraverse(G); break;
		case 4:Prim(G); break;
		default:printf("输入错误!!!\n"); break;
		}
	}
	return 0;
}
Kruskal算法

Kruskal算法是一种按照网中边的权值递增的顺序构造最小生成树的方法。
Kruskal算法基本思想
假设连通网G=(V,E),令最小生成树的初始状态为只有n个顶点而无边的非连通图T=(V,{}),图中每个顶点自成一个连通分量。在E中选择权值最小的边,若该边依附的顶点落在T中不同的连通分量中,则将此边加入到T中;否则,舍去此边而选下一条权值最小的边;
依次类推,直到T中所有顶点都在同一个连通分量上(此时含有n-1边)为止,这时的T就是一棵最小的生成树。
在这里插入图片描述

/*
基本操作函数:
InitGraph(Graph &G)             初始化函数 参数:图G 作用:初始化图的顶点表,邻接矩阵等
InsertNode(Graph &G,VexType v) 插入点函数 参数:图G,顶点v 作用:在图G中插入顶点v,即改变顶点表
InsertEdge(Graph &G,VexType v,VexType w) 插入边函数 参数:图G,某边两端点v和w 作用:在图G两点v,w之间加入边,即改变邻接矩阵
Adjancent(Graph G,VexType v,VexType w) 判断是否存在边(v,w)函数 参数:图G,某边两端点v和w 作用:判断是否存在边(v,w)
BFS(Graph G, int start)      广度遍历函数 参数:图G,开始结点下标start 作用:宽度遍历
DFS(Graph G, int start)      深度遍历函数(递归形式)参数:图G,开始结点下标start 作用:深度遍历
功能实现函数:
CreateGraph(Graph &G) 创建图功能实现函数 参数:图G  InsertNode 作用:创建图
BFSTraverse(Graph G)  广度遍历功能实现函数 参数:图G 作用:宽度遍历
DFSTraverse(Graph G)  深度遍历功能实现函数 参数:图G 作用:深度遍历
Kruskal(Graph G) 最小生成树-Kruskal算法 参数:图G
*/
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<string>
#include<set>
#include<list>
#include<queue>
#include<vector>
#include<map>
#include<iterator>
#include<algorithm>
#include<iostream>
#define MaxVerNum 100 //顶点最大数目值
#define VexType char //顶点数据类型
#define EdgeType int //边数据类型,无向图时邻接矩阵对称,有权值时表示权值,没有时1连0不连
#define INF 0x3f3f3f3f//作为最大值
using namespace std;
//图的数据结构
typedef struct Graph
{
	VexType Vex[MaxVerNum];//顶点表
	EdgeType Edge[MaxVerNum][MaxVerNum];//边表
	int vexnum, arcnum;//顶点数、边数
}Graph;

//Kruskal算法所用数据结构
typedef struct Edge
{
	int from;   //起点下标
	int to;     //终点下标
	int weight; //权值
};
vector<Edge> l;
//按权值比较
bool cmp(Edge e1, Edge e2)
{
	if (e1.weight < e2.weight)
	{
		return true;
	}
	return false;
}
//*********************************************基本操作函数*****************************************//
//初始化函数 参数:图G 作用:初始化图的顶点表,邻接矩阵等
void InitGraph(Graph& G)
{
	memset(G.Vex, '#', sizeof(G.Vex));//初始化顶点表
									  //初始化边表
	for (int i = 0; i < MaxVerNum; i++)
		for (int j = 0; j < MaxVerNum; j++)
		{
			G.Edge[i][j] = INF;
			if (i == j)G.Edge[i][j] = 0;//在最小生成树时,考虑无环简单图,故自己到自己设置为0
		}
	G.arcnum = G.vexnum = 0;          //初始化顶点数、边数
}
//插入点函数 参数:图G,顶点v 作用:在图G中插入顶点v,即改变顶点表
bool InsertNode(Graph& G, VexType v)
{
	if (G.vexnum < MaxVerNum)
	{
		G.Vex[G.vexnum++] = v;
		return true;
	}
	return false;
}
//插入边函数 参数:图G,某边两端点v和w 作用:在图G两点v,w之间加入边,即改变邻接矩阵
bool InsertEdge(Graph& G, VexType v, VexType w, int weight)
{
	int p1, p2;//v,w两点下标
	p1 = p2 = -1;//初始化
	for (int i = 0; i < G.vexnum; i++)//寻找顶点下标
	{
		if (G.Vex[i] == v)p1 = i;
		if (G.Vex[i] == w)p2 = i;
	}
	if (-1 != p1 && -1 != p2)//两点均可在图中找到
	{
		G.Edge[p1][p2] = G.Edge[p2][p1] = weight;//无向图邻接矩阵对称
		G.arcnum++;
		//Kruskal算法增加代码
		Edge e;
		e.from = p1;
		e.to = p2;
		e.weight = weight;
		l.push_back(e);
		return true;
	}
	return false;
}
//判断是否存在边(v,w)函数 参数:图G,某边两端点v和w 作用:判断是否存在边(v,w) 
bool Adjancent(Graph G, VexType v, VexType w)
{
	int p1, p2;//v,w两点下标
	p1 = p2 = -1;//初始化
	for (int i = 0; i < G.vexnum; i++)//寻找顶点下标
	{
		if (G.Vex[i] == v)p1 = i;
		if (G.Vex[i] == w)p2 = i;
	}
	if (-1 != p1 && -1 != p2)//两点均可在图中找到
	{
		if (G.Edge[p1][p2] == 1)//存在边
		{
			return true;
		}
		return false;
	}
	return false;
}
bool visited[MaxVerNum];//访问标记数组,用于遍历时的标记
//广度遍历函数 参数:图G,开始结点下标start 作用:宽度遍历
void BFS(Graph G, int start)
{
	queue<int> Q;//辅助队列
	cout << G.Vex[start];//访问结点
	visited[start] = true;
	Q.push(start);//入队
	while (!Q.empty())//队列非空
	{
		int v = Q.front();//得到队头元素
		Q.pop();//出队
		for (int j = 0; j < G.vexnum; j++)//邻接点
		{
			if (G.Edge[v][j] < INF && !visited[j])//是邻接点且未访问
			{
				cout << "->";
				cout << G.Vex[j];//访问结点
				visited[j] = true;
				Q.push(j);//入队
			}
		}
	}//while
	cout << endl;
}
//深度遍历函数(递归形式)参数:图G,开始结点下标start 作用:深度遍历
void DFS(Graph G, int start)
{
	cout << G.Vex[start];//访问
	visited[start] = true;
	for (int j = 0; j < G.vexnum; j++)
	{
		if (G.Edge[start][j] < INF && !visited[j])//是邻接点且未访问
		{
			cout << "->";
			DFS(G, j);//递归深度遍历
		}
	}
}

//**********************************************功能实现函数*****************************************//
//打印图的顶点表
void PrintVex(Graph G)
{
	for (int i = 0; i < G.vexnum; i++)
	{
		cout << G.Vex[i] << " ";
	}
	cout << endl;
}
//打印图的边矩阵
void PrintEdge(Graph G)
{
	for (int i = 0; i < G.vexnum; i++)
	{
		for (int j = 0; j < G.vexnum; j++)
		{
			if (G.Edge[i][j] == INF)cout << "∞ ";
			else cout << G.Edge[i][j] << " ";
		}
		cout << endl;
	}
}
//创建图功能实现函数 参数:图G  InsertNode 作用:创建图
void CreateGraph(Graph& G)
{
	VexType v, w;
	int vn, an;//顶点数,边数
	cout << "请输入顶点数目:" << endl;
	cin >> vn;
	cout << "请输入边数目:" << endl;
	cin >> an;
	cout << "请输入所有顶点名称:" << endl;
	for (int i = 0; i < vn; i++)
	{
		cin >> v;
		if (InsertNode(G, v)) continue;//插入点
		else {
			cout << "输入错误!" << endl; break;
		}
	}
	cout << "请输入所有边(每行输入边连接的两个顶点及权值):" << endl;
	for (int j = 0; j < an; j++)
	{
		int weight;
		cin >> v >> w >> weight;
		if (InsertEdge(G, v, w, weight)) continue;//插入边
		else {
			cout << "输入错误!" << endl; break;
		}
	}
	PrintVex(G);
	PrintEdge(G);
}
//广度遍历功能实现函数 参数:图G 作用:宽度遍历
void BFSTraverse(Graph G)
{
	for (int i = 0; i < MaxVerNum; i++)//初始化访问标记数组
	{
		visited[i] = false;
	}
	for (int i = 0; i < G.vexnum; i++)//对每个连通分量进行遍历
	{
		if (!visited[i])BFS(G, i);
	}
}
//深度遍历功能实现函数 参数:图G 作用:深度遍历
void DFSTraverse(Graph G)
{
	for (int i = 0; i < MaxVerNum; i++)//初始化访问标记数组
	{
		visited[i] = false;
	}
	for (int i = 0; i < G.vexnum; i++)//对每个连通分量进行遍历
	{
		if (!visited[i])
		{
			DFS(G, i); cout << endl;
		}
	}
}

//最小生成树-Kruskal算法
void Kruskal(Graph G)
{
	//初始化
	sort(l.begin(), l.end(), cmp);
	int verSet[MaxVerNum];
	int mincost = 0;
	for (int i = 0; i < G.vexnum; i++)
		verSet[i] = i;
	cout << "最小生成树所有边:" << endl;
	//依次查看边
	int all = 0;
	for (int i = 0; i < G.arcnum; i++)
	{
		if (all == G.vexnum - 1)break;
		int v1 = verSet[l[i].from];
		int v2 = verSet[l[i].to];
		//该边连接两个连通分支
		if (v1 != v2)
		{
			cout << "(" << l[i].from << "," << l[i].to << ") ";
			mincost += l[i].weight;
			//合并连通分支
			for (int j = 0; j < G.vexnum; j++)
			{
				if (verSet[j] == v2)verSet[j] = v1;
			}
			all++;
		}
	}
	cout << "最小生成树权值之和:" << mincost << endl;
}
//菜单
void menu()
{
	cout << "************1.创建图           2.广度遍历******************" << endl;
	cout << "************3.深度遍历         4.最小生成树(Kruskal)*******" << endl;
	cout << "************5.退出             ****************************" << endl;
}
//主函数
int main()
{
	int choice = 0;
	Graph G;
	InitGraph(G);
	while (1)
	{
		menu();
		printf("请输入菜单序号:\n");
		scanf("%d", &choice);
		if (5 == choice) break;
		switch (choice)
		{
		case 1:CreateGraph(G); break;
		case 2:BFSTraverse(G); break;
		case 3:DFSTraverse(G); break;
		case 4:Kruskal(G); break;
		default:printf("输入错误!!!\n"); break;
		}
	}
	return 0;
}

最短路径

最短路径问题:如果从图中某一顶点(称为源点)到达另一顶点(称为终点)的路径可能不止一条,找到一条路径使得沿此路径上各边上的权值总和达到最小。 (dijkstra算法和flord算法)

拓扑排序

AOV网与拓扑排序

1.AOV网 (顶点表示活动的网)
(1) AOV概念:顶点表示活动,弧表示活动之间存在的制约关系网称为AOV。
(2) 用AOV表示一个工程.

2.拓扑排序概念
(1)偏序集合概念
若集合A中的二元关系R是自反的、非对称的和传递的,则R是A上的偏序关系。集合A与关系R一起称为一个偏序集合。
(2) 全序集合概念
若R是集合A上的一个偏序关系,如果对每个a、b∈A必有aRb或bRa ,则R是A上的全序关系。集合A与关系R一起称为一个全序集合。
偏序关系经常出现在我们的日常生活中,例如,若把A看成一项大的工程必须完成的一批活动,则aRb意味着活动a必须在活动b之前完成。
AOV网所代表的一项工程中活动的集合显然是一个偏序集合。为了保证该项工程得以顺利完成,必须保证AOV网中不出现回路。
测试AOV网是否具有回路的方法,就是在AOV网的偏序集合下构造一个线性序列。即拓扑排序。

3.拓扑排序
满足这样性质的线性序列称为拓扑有序序列:
①在AOV网中,若顶点i 优先于顶点j ,则在线性序列中顶点i仍然优先于顶点j;
②对于网中原来没有优先关系的顶点与顶点,在线性序列中也建立一个先后关系,或者顶点i优先于顶点j ,或者顶点j 优先于i。

4.对AOV网进行拓扑排序的方法和步骤是:
①从AOV网中选择一个没有前驱的顶点(该顶点的入度为0)并且输出它;
②从网中删去该顶点,并且删去从该顶点发出的全部有向边;
③重复上述两步,直到剩余的网中不再存在没有前驱的顶点为止。
这样操作的结果有两种:一是网中全部顶点都被输出,这说明网中不存在有向回路;一是网中顶点未全部输出,剩余的顶点均不前驱顶点,这说明网中存在有向回路。
在这里插入图片描述

AOE图与关键路径

1.AOE网(边表示活动的网)
(1) AOE网概念:若在带权的有向图中,以顶点表示事件,以有向边表示活动,边上的权值表示活动的开销(如该活动持续的时间),则此带权的有向图称为AOE网。
(2)AOE网表示一项工程能表示出:
①完成预定工程计划所需要进行的活动;
②每个活动计划完成的时间;
③要发生哪些事件以及这些事件与活动之间的关系;
(3) 通过AOE网可以求得:
①估算工程完成的时间
②确定哪些活动是影响工程进度的关键。
(4) AOE网的两个特点:
①只有在某顶点所代表的事件发生后,从该顶点出发的各有向边所代表的活动才能开始。
②只有在进入一某顶点的各有向边所代表的活动都已经结束,该顶点所代表的事件才能发生。
在这里插入图片描述

2.关键路径
具有最大路径长度的路径称为关键路径。关键路径上的活动称为关键活动。
关键路径长度就是整个工程所需的最短工期。
在这里插入图片描述

4.哪些是关键活动?根据每个活动的最早开始时间e[i]和最晚开始时间l[i]就可判定该活动是否为关键活动,也就是那些l[i]=e[i]的活动就是关键活动,而那些l[i]>e[i]的活动则不是关键活动,l[i]-e[i]的值为活动的时间余量。关键活动确定之后,关键活动所在的路径就是关键路径。 在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

第六章 查找

概念

查找表

(1) 查找表是一种以集合为逻辑结构、以查找为核心的数据结构。
(2) 由于集合中的数据元素之间是没有“关系”的,因此在查找表的实现时就不受“关系”的约束,而是根据实际应用对查找的具体要求去组织查找表,以便实现高效率的查找。
(3) 对查找表中常做的运算有:建立查找表、查找、读取表元,以及对表做修改操作(如插入、删除元素)等等。

关键码

关键码是数据元素(记录)中某个数据项的值,用它可以标识一个数据元素。
能唯一确定一个数据元素的关键码称为主关键码;不能唯一确定一个数据元素的关键码称为次关键码。

查找

查找:是指在含有n个元素的查找表中,找出关键码等于给定值kx的数据元素(或记录)。
当要查找的关键码是主关键码时,查找结果是唯一的;
当要查找的关键码是次关键码时,查找结果可能不唯一;

静态查找与动态查找

若对查找表的查不包对表的修改操作,则此类查找称为静态查找。
若在查找的同时插入表中不存在的数据元素,或从查找表中删除已存在的指定元素,则此类查找称为动态查找。

平均查找长度(衡量查找效率的指标)

由于查找运算的主要操作是关键字的比较,所以,通常把查找过程中对关键字的比较次数的平均值作为衡量一个查找算法效率优劣的标准,称之为平均查找长度(Average Search Length),通常用ASL表示。
在这里插入图片描述
在这里插入图片描述

线性表查找

顺序查找

顺序表上的查找

顺序查找又称线性查找,是最基本的查找方法之一:
其查找方法为:
从表的一端开始,向另一端逐个按给定值kx与关键码进行比较,若找到,查找成功,并给出数据元素在表中的位置;
若整个表检测完,仍未找到与kx相同的关键码,则查找失败,给出失败信息。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

单链表上的查找

在这里插入图片描述

在顺序存储的有序表上查找

折半查找(二分查找,时间复杂度为O(log2n))

条件:查找表按关键码有序且顺序存储
在这里插入图片描述

插值查找(时间复杂度: O(log2n) )

插值查找除要求查找表是顺序存储的有序表外,还要求数据元素的关键字在查找表中均匀分布。
插值查找通过下列公式求取中间点:
在这里插入图片描述

//插值查找
int InsertionSearch(int a[], int value, int low, int high)
{
    int mid = low+(value-a[low])/(a[high]-a[low])*(high-low);
    if(a[mid]==value)
        return mid;
    if(a[mid]>value)
        return InsertionSearch(a, value, low, mid-1);
    if(a[mid]<value)
        return InsertionSearch(a, value, mid+1, high);
}
斐波纳契查找(时间复杂度: O(log2n) )

使用条件:顺序存储且关键码有序。
按斐波纳契数列将有序的查找便分割成两个不等的区间:
设n个数据元素的有序表,且n正好是某个斐波那契数-1,即n=F(K)-1时,可用此查找方法。
在这里插入图片描述

int FibonacciSearch(int* a, int key)  //a为要查找的数组,key为要查找的关键字
{
    Fibonacci(F);//构造一个斐波那契数组F
    int t;
    int low = 1, high = F(k) - 1;//设置初始空间
    int len = F(k) - 1, f = F(k - 1) - 1;//len为表长,f为取中点的相对偏移量
    while (low <= high)
    {
        int mid = low + f;
        if (key == a[mid])return mid;
        else
            if (key < a[mid])
            {
                high = mid - 1;
                t = f;
                f = len - f - 1;
                len = t;
            }
            else
            {
                low = mid + 1;
                len = len - f - 1;
                f = f - len - 1;
            }
    }
    return 0;
}

分块查找

若查找表中的数据元素的关键字是按块有序的,则可以做分块查找。分块查找又称索引顺序查找。
分块查找将查找表按有序块分成若干个子表,对每个子表建立一个索引项,再将这些索引项顺序存储,形成一个索引表。
查找过程:
(1)首先根据给定值kx在索引表中查找,以确定属于查找表中的哪一块,可用顺序查找或拆半查找。
(2)然后,在块内查找,用顺序查找。

树表查找

二叉排序树

二叉排序树定义

或者是一棵空树,或者是满足如下性质的二叉树:
1)若左子树不空,则左子树上所有结点的值均小于根节点的值;若右子树不空,则右子树上所有结点的值均大于根节点的值;
2)左右子树也分别是二叉排序树。
特点:中序遍历序列关键码有序。

二叉排序树的查找

在这里插入图片描述
在这里插入图片描述

在二叉排序树中插入一个结点

设待插入结点的关键码为kx,为将其插入,先要在二叉排序树中进行查找,若查找成功,按二叉排序树定义,待插入结点已存在,不用插入;
查找不成功时,则插入。
因此,新插入结点一定是作为叶子结点添加上去的。
在这里插入图片描述

二叉排序树中删除一个结点

设待删结点为p(p为指向待删结点的指针),其双亲结点为f,以下分三种情况进行讨论。
(1)p结点为叶结点:
在这里插入图片描述

(2)p结点只有右子树pR或只有左子树pL。
只需将pR或pL替换f结点的p子树即可。
在这里插入图片描述

(3)p结点既有左子树PL又有右子树PR,可按中序遍历保持有序进行调整。
在这里插入图片描述
删除p结点后,有2种调整方法:
①直接令p结点的右孩子p1为f相应的子树,将p的原左子树PL作为以p1为根的子树中序遍历的第一个结点 pr的左子树。
在这里插入图片描述
②令p结点的中序直接后继PR(或中序直接前驱)替换p结点,这个结点只能是叶子或单支结点,再按1)或2)的方法删去PR。
在这里插入图片描述

综合代码
/*
BST_Insert(BSTree &T, TElemType data)  参数T,二叉查找树根节点 作用:插入数据data,保证中序非严格递增(即可重复)
PreOrder(BSTree T)  参数T,二叉查找树根节点 作用:先序遍历二叉查找树,递归方式
InOrder(BSTree T)   参数T,二叉查找树根节点 作用:中序遍历二叉查找树,递归方式
PostOrder(BSTree T) 参数T,二叉查找树根节点 作用:后序遍历二叉查找树,递归方式
LevelOrder(BSTree T)参数T,二叉查找树根节点 作用:层序遍历二叉查找树,递归方式
BST_Search(BSTree T, TElemType key) 参数 BSTree T, TElemType key,作用查找key是否存在于二叉查找树中,若存在,返回指向该结点的指针,否则,返回空
BST_Delete(BSTree &T, TElemType key) 参数 BSTree T, TElemType key,作用查找key是否存在于二叉查找树中,若存在,删除,返回true,否则,返回false
CreateBSTree(BSTree &T) 参数T,二叉查找树根节点 作用:调用InitTree,创建二叉查找树
Traverse(BSTree T)      参数T,二叉查找树根节点 作用:PreOrder InOrder PostOrder LevelOrder遍历二叉查找树
InsertBSTree(BSTree &T) 参数T,二叉查找树根节点 作用:插入结点,调用BST_Insert
DeleteBSTree(BSTree &T) 参数T,二叉查找树根节点 作用:删除结点,调用BST_Delete
SearchBSTree(BSTree T)  参数T,二叉查找树根节点 作用:查找结点,调用BST_Search
*/
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<string>
#include<stack>
#include<queue>
#include<algorithm>
#include<iostream>
#define TElemType char
using namespace std;
//链式二叉查找树数据结构
typedef struct BSTNode
{
	TElemType data;//数据域
	struct BSTNode* lchild, * rchild;//左右孩子
}BSTNode, * BSTree;

//********************************基本操作函数********************************//
// 
//二叉查找树插入 参数T 二叉查找树根节点 作用:插入数据data,保证中序非严格递增(即可重复)
int BST_Insert(BSTree& T, TElemType data)
{
	if (T == NULL)//树为空
	{
		T = (BSTree)malloc(sizeof(BSTNode));//申请结点空间
		T->data = data;                     //赋值
		T->lchild = NULL;                   //左右孩子为空
		T->rchild = NULL;
		return 1;
	}
	else if (data < T->data)               //比当前节点数据小,插入左子树
	{
		return BST_Insert(T->lchild, data);
	}
	else                                   //这里规定二叉查找树可以重复,等于及大于当前结点时,插入右子树
	{
		return BST_Insert(T->rchild, data);
	}
}
//先序遍历-递归
void PreOrder(BSTree T)
{
	if (T != NULL)
	{
		cout << T->data << " ";
		PreOrder(T->lchild);//递归先序遍历左右子树
		PreOrder(T->rchild);
	}
}
//中序遍历-递归
void InOrder(BSTree T)
{
	if (T != NULL)
	{
		InOrder(T->lchild);//递归中序遍历左右子树
		cout << T->data << " ";
		InOrder(T->rchild);
	}
}
//后序遍历-递归
void PostOrder(BSTree T)
{
	if (T != NULL)
	{
		PostOrder(T->lchild);//递归后序遍历左右子树
		PostOrder(T->rchild);
		cout << T->data << " ";
	}
}
//层序遍历 借助队列
void LevelOrder(BSTree T)
{
	queue<BSTNode> q;//借助队列
	if (T != NULL)
	{
		BSTNode temp;//暂存要出队的结点
		q.push(*T);//根结点入队
		while (!q.empty())//队列非空
		{
			temp = q.front();
			q.pop();
			cout << temp.data << " ";
			if (temp.lchild != NULL) q.push(*temp.lchild);//队列先进先出,先入左孩子
			if (temp.rchild != NULL) q.push(*temp.rchild);
		}
	}
}
//二叉查找树查找 参数 BSTree T, TElemType key,作用查找key是否存在于二叉查找树中,若存在,返回指向该结点的指针,否则,返回空
BSTNode* BST_Search(BSTree T, TElemType key)
{
	BSTNode* cur = T; //当前结点指针cur
	while (cur && key != cur->data)
	{
		if (key < cur->data)     //比当前结点数据小
		{
			cur = cur->lchild;
		}
		else                  //比当前结点数据大
		{
			cur = cur->rchild;
		}
	}
	return cur;
}
//二叉查找树删除 参数 BSTree T, TElemType key,作用查找key是否存在于二叉查找树中,若存在,删除,返回true,否则,返回false
bool BST_Delete(BSTree& T, TElemType key)
{
	BSTNode* cur = T;
	BSTNode* cur_par = NULL;//当前结点的双亲结点
	while (cur && key != cur->data)
	{
		if (key < cur->data)     //比当前结点数据小
		{
			cur_par = cur;    //记录双亲结点
			cur = cur->lchild;
		}
		else                  //比当前结点数据大
		{
			cur_par = cur;    //记录双亲结点
			cur = cur->rchild;
		}
	}
	if (cur)//找到啦,分情况删除:叶子结点(度为0) 单支结点(度为1) 双支结点(度为2)
	{
		if (cur->lchild == NULL && cur->rchild == NULL)//叶子结点
		{
			if (cur == T)//根节点
			{
				T = NULL;
			}
			else if (cur_par->lchild == cur)//要删除的是cur_par的左孩子
			{
				cur_par->lchild = NULL;
			}
			else
			{
				cur_par->rchild = NULL;
			}
		}
		else if (cur->lchild == NULL || cur->rchild == NULL)//单支结点
		{
			if (cur == T)//根节点
			{
				if (cur->lchild)//有左孩子
				{
					T = cur->lchild;
				}
				else
				{
					T = cur->rchild;
				}
			}
			else //非根结点,双亲结点指向要删除结点的子结点即可
			{
				if (cur_par->lchild == cur && cur->lchild)//cur为cur的双亲结点的左孩子,且有左孩子
				{
					cur_par->lchild = cur->lchild;
				}
				else if (cur_par->lchild == cur && cur->rchild)
				{
					cur_par->lchild = cur->rchild;
				}
				else if (cur_par->rchild == cur && cur->lchild)
				{
					cur_par->rchild = cur->lchild;
				}
				else {
					cur_par->rchild = cur->rchild;
				}
			}
		}
		else //双支结点  可以选择与直接前驱交换数据域,然后删除直接前驱。
			//或者,与直接后继交换数据域,删除直接后继。这里选择后者。
		{
			BSTNode* temp = cur;//记录需要删除的结点,接下来要与直接后继交换数据域
			cur_par = cur;      //用cur找到temp的直接后继 则cur为temp右子树的最左孩子
			cur = cur->rchild;  //右子树
			while (cur->lchild)//找到直接后继,即右子树的最左孩子(最小值)
			{
				cur_par = cur;
				cur = cur->lchild;
			}
			temp->data = cur->data;//交换数据域
			if (cur_par == temp)//待删除结点的右子树没有左子树,即右子树根节点即为待删除结点后继
			{
				cur_par->rchild = cur->rchild;
			}
			else               //待删除结点的右子树有左子树
			{
				cur_par->lchild = cur->rchild;//将cur的右子树给双亲结点
			}
		}
		free(cur);
		return true;
	}
	return false;
}
//**********************************功能实现函数*****************************//
//调用BST_Insert进行二叉查找树的创建
void CreateBSTree(BSTree& T)
{
	string s;
	printf("请输入二叉树结点数据创建二叉查找树(结点字符串):\n");
	cin >> s;
	for (int i = 0; i < s.length(); i++)
	{
		BST_Insert(T, s[i]);
	}
	printf("二叉查找树先序遍历序列:");
	PreOrder(T);
	printf("\n");
}
//遍历功能函数 调用PreOrder InOrder PostOrder LevelOrder
void Traverse(BSTree T)
{
	int choice;
	while (1)
	{
		printf("********1.先序遍历    2.中序遍历*********\n");
		printf("********3.后序遍历    4.层次遍历*********\n");
		printf("********5.返回上一单元\n");
		printf("请输入菜单序号:\n");
		scanf("%d", &choice);
		if (5 == choice) break;
		switch (choice)
		{
		case 1: {printf("二叉查找树先序遍历序列:"); PreOrder(T); printf("\n"); }break;
		case 2: {printf("二叉查找树中序遍历序列:"); InOrder(T); printf("\n"); }break;
		case 3: {printf("二叉查找树后序遍历序列:"); PostOrder(T); printf("\n"); }break;
		case 4: {printf("二叉查找树层次遍历序列:"); LevelOrder(T); printf("\n"); }break;
		default:printf("输入错误!!!\n"); break;
		}
	}
}
//插入 调用BST_Insert
void InsertBSTree(BSTree& T)
{
	TElemType data;
	printf("请输入要插入的数据:\n");
	cin >> data;
	BST_Insert(T, data);
	printf("插入成功!!!\n");
}
//删除 调用BST_Delete
void DeleteBSTree(BSTree& T)
{
	TElemType key;
	printf("请输入要删除的数据:\n");
	cin >> key;
	if (BST_Delete(T, key))
	{
		printf("删除成功!\n");
	}
	else
	{
		printf("未找到!\n");
	}
}
//查找 调用BST_Search
void SearchBSTree(BSTree T)
{
	TElemType data;
	BSTNode* p;
	printf("请输入要查找的数据:\n");
	cin >> data;
	p = BST_Search(T, data);
	if (p)
	{
		printf("该数据存在!!!\n");
	}
	else
		printf("该数据不存在!!!\n");
}
//菜单
void menu()
{
	printf("********1.创建    2.遍历*********\n");
	printf("********3.插入    4.查找*********\n");
	printf("********5.删除    6.退出*********\n");
}
//主函数
int main()
{
	BSTree T = NULL; 
	int choice = 0;
	while (1)
	{
		menu();
		printf("请输入菜单序号:\n");
		scanf("%d", &choice);
		if (6 == choice) break;
		switch (choice)
		{
		case 1:CreateBSTree(T); break;
		case 2:Traverse(T); break;
		case 3:InsertBSTree(T); break;
		case 4:SearchBSTree(T); break;
		case 5:DeleteBSTree(T); break;
		default:printf("输入错误!!!\n"); break;
		}
	}
	return 0;
}

平衡二叉树

定义

它或者是一棵空树,或者是具有下列性质的二叉排序树:
(1)根结点的平衡因子绝对值不超过1;
(2)其左子树和右子树都是平衡二叉树。
结点的平衡因子指该结点的左子树高度与右子树高度之差。
非平衡二叉树在这里插入图片描述
平衡二叉树
在这里插入图片描述

平衡二叉树的插入及平衡调整

在平衡二叉树上插入或删除结点后,可能使二叉树失去平衡
。因此需要对失去平衡的树进行平衡化调整,调整过程归纳起来有以下四种情况:
(1)在某结点左孩子的左子树上插入结点使该结点失衡(LL);
(2)在某结点右孩子的右子树上插入结点使某结点失衡(RR);
(3)在某结点左孩子的右子树上插入结点使某结点失衡(LR);
(4)在某结点右孩子的左子树上插入结点使某结点失衡(RL)。

LL型:做右单旋转调整

失衡原因:在失衡结点的左孩子的左子树上插入结点造成的。
在这里插入图片描述

RR型:做左单旋转调整

失衡原因:在失衡结点的右孩子的右子树上插入结点造成的。
在这里插入图片描述

LR型:做先左后右双向旋转调整

失衡原因:在失衡结点的左孩子的右子树上插入结点造成的。
在这里插入图片描述

RL型:做先右后左双向旋转

失衡原因:在失衡结点的右孩子的左子树上插入结点造成的。
在这里插入图片描述

散列表查找

概念

1.散列是一种存储策略,散列表也叫哈希(hash)表、杂凑表,是基于散列存储策略建立的查找表。

2.冲突
对于n个数据元素的集合,总能找到关键码与存放地址一一对应的函数。
如若最大关键码为m,可以分配m个数据元素存放单元,选取函数f(key)=key即可,但这样会造成存储空间的很大浪费,甚至不可能分配这么大的存储空间。
通常关键码的集合比哈希地址集合大得多,因而经过哈希函数变换后,可能将不同的关键码映射到同一个哈希地址上,这种现象称为冲突,映射到同一哈希地址上的关键码称为同义词。
可以说,冲突不可能避免,只能尽可能减少。

3.哈希方法需要解决以下两个问题:
(1) 构造好的哈希函数
1)所选函数尽可能简单,以便提高转换速度。
2)所选函数对关键码计算出的地址,应在哈希地址集中大致均匀分布,以减少空间浪费。
(2) 制定解决冲突的方案

常用的哈希函数

直接定址法(线性函数)

Hash(key)=a·key+b (a、b为常数)
即取关键码的某个线性函数值为哈希地址,这类函数是一一对应函数,不会产生冲突,但要求地址空间与关键码涉及到的地址空间大小相同,因此,对于较大的关键码分布范围大的不适用。
在这里插入图片描述

除留余数法

Hash(key)=key mod p (p是一个整数)
即取关键码除以p的余数作为哈希地址。使用除留余数法,选取合适的p很重要,若哈希表表长为m,则要求p≤m,且接近m或等于m。
p一般选取<=m的最大质数。

乘余取整法

在这里插入图片描述
(A、B均为常数,且0<A<1,B为整数)
一般较为理想取是: 在这里插入图片描述

数字分析法

设关键码集合中,每个关键码均由m位组成,每位上可能有r种不同的符号。
在这里插入图片描述

平方取中法

对关键码平方后,按哈希表大小,取中间的若干位作为哈希地址。

折叠法(Folding)

此方法将关键码自左到右分成位数相等的几部分,最后一部分位数可以短些,然后将这几部分叠加求和,并按哈希表表长,取后几位作为哈希地址。这种方法称为折叠法。
有两种叠加方法:
(1) 移位法 ── 将各部分的最后一位对齐相加。
(2) 间界叠加法 ── 从一端向另一端沿各部分分界来回折叠后,最后一位对齐相加。
在这里插入图片描述

处理冲突的方法及散列表的构造

开放定址法

开放定址法解决冲突的思想是:由关键码得到的散列地址一旦产生了冲突,也就是说该地址已经存放了数据元素,就按照一个探测序列去寻找下一个空的散列地址空间,只要散列表足够大,空的散列地址总能找到,并将数据元素存入。
用开放定址法解决冲突建立的散列表也叫闭散列表。
闭散列表的存储结构一般采用具有m个数据元素空间的向量。
形成探测序列的方法有很多种,下面介绍3种:

线性探测法

发生冲突后的探测序列为:
Hi=(Hash(key)+di) mod m ( 1≤i < m )
其中: Hash(key)为哈希函数
m为哈希表长度
di 为增量序列 1,2,……,m-1,且di=I
Hash(key)+1、Hash(key)+2、…
在这里插入图片描述
在这里插入图片描述
堆积问题的产生
线性探测法可能使第i个哈希地址的同义词存入第i+1个哈希地址。
这样本应存入第i+1个哈希地址的元素变成了第i+2个哈希地址的同义词,……,因此,可能出现很多元素在相邻的哈希地址上“堆积”起来,大大降低了查找效率。
为此,可采用二次探测法,或双哈希函数探测法,以改善“堆积”问题。

二次探测法

发生冲突后的探测序列为:
Hi=(Hash(key)±di) mod m
di 为增量序列 12,-12,22,-22,……, q2,-q2
在这里插入图片描述

双哈希函数探测法

Hi=(Hash(key)+i*ReHash(key))mod m (i=1,2,……,m-1)
其中:Hash(key),ReHash(key)是两个哈希函数, m为哈希表长度
双哈希函数探测法:先用第一个函数Hash(key)对关键码计算哈希地址,一旦产生地址冲突,再用第二个函数ReHash(key)确定移动的步长因子,最后,通过步长因子序列由探测函数寻找空的哈希地址。
比如,Hash(key)=a时产生地址冲突,就计算ReHash(key)=b,则探测的地址序列为:
H1=(a+b) mod m,H2=(a+2b) mod m,……,
Hm-1=(a+(m-1)b) mod m

第七章 排序

插入排序

插入排序的基本思想是:每次将一个待排序的记录,按其关键字大小插入到前面已经排好序的子表的适当位置,直到全部记录插入完成,整个表有序为止。

直接插入排序

直接插入排序是一种简单的插入排序方法,基本思想为:在R[1]至R[i-1]长度为i-1的子表已经有序的情况下,将R[i]插入,得到R[1]至R[i]长度为i 的子表有序,这样通过n-1趟(i=2…n)之后,R[1]至R[n]有序。
直接插入排序:仅有一个记录的表总是有序的,因此,对n个记录的表,可从第二个记录开始直到第n个记录,逐个向有序表中进行插入操作,从而得到n个记录按关键码有序的表。
在这里插入图片描述

折半插入排序

直接插入排序的基本操作是向有序表中插入一个记录,在直接插入排序中,插入位置的确定是通过对有序表中关键码的顺序比较得到的。
在这里插入图片描述
折半插入排序是一个稳定的排序方法。
折半插入排序只适合于顺序存储的排序表。

希尔排序

直接插入排序算法简单,特点为:在n值较小时,效率比较高,在n值很大时,若序列按关键码基本有序,效率依然较高,其时间效率可提高到O(n)。
希尔排序(Shell’s Sort)是从这两点出发,给出插入排序的改进方法。希尔排序又称缩小增量排序。
在有序性差时尽量不让排序表太大,
随着有序性变好,排序表逐渐加大。
希尔排序的思想是:
先选取一个小于n的整数di(称之为步长),然后把排序表中的n个记录分为di个组,从第一个记录开始,间隔为di的记录为同一组,各组内进行直接插入排序,一趟之后,间隔di的记录有序,随着有序性的改善,减小步长di(排序子表变大),重复进行,直到di=1(全部记录成为一个排序表),使得间隔为1的记录有序,也就使整体达到了有序。
步长为1时就是前面讲的直接插入排序。

希尔排序中关键码的比较次数与记录移动次数还依赖于步长因子序列的选取。
目前还没有人给出选取最好的步长因子序列的方法。
但需要注意:最后一个步长因子必须为1。
希尔排序方法是一种不稳定的排序方法。

交换排序

交换排序的基本思想是:通过排序表中两个记录关键码的比较,若与排序要求相逆,则将二者进行交换,直至没有反序的记录为止。
交换排序的特点是:排序码值较小记录的向序列的一端移动,排序码值较大记录的向序列的另一端移动。

冒泡排序

设排序表为R[1]…R[n],对n个记录的排序表进行冒泡排序(Bubble Sort)的过程是:
第1趟,从第1个记录开始到第n个记录,对n1对相邻的两个记录关键字进行比较,若与排序要求相逆,则将二者交换。一趟之后,具有最大关键字的记录交换到了R[n],
第2趟,从第1个记录开始到第n1个记录继续进行第二趟冒泡。两趟之后,具有次最大关键字的记录交换到了R[n1],…
如此重复,n1趟后,在R[1]…R[n]中,n个记录按关键码有序。
冒泡排序最多进行 n1趟,在某趟的两两比较过程中,如果一次交换都未发生,表明已经有序,则排序提前结束。
在这里插入图片描述

快速排序

快速排序的思想
快速排序是通过比较关键码、交换记录,以某个记录为界(该记录称为支点),将待排序列分成两部分。其中,一部分所有记录的关键码大于等于支点记录的关键码,另一部分所有记录的关键码小于支点记录的关键码。
我们将待排序列按关键码以支点记录分成两部分的过程,称为一次划分。
对各部分不断划分,直到整个序列按关键码有序。
在这里插入图片描述
在这里插入图片描述

选择排序

选择排序是在每一趟排序中通过关键码比较,选择出关键码值最小(以正序为例)的记录,输出或置入相应的位置上,然后再从其余元素中选择最小。
第1趟,从n个记录中找出关键码最小的记录;
第2趟,从剩余的n-1个记录中找出关键码最小的记录;

第n-1趟,从剩余的2个记录中找出关键码最小的记录。
排序结束。
故选择排序需要n-1趟。

简单选择排序

7.4.1 简单选择排序

基本思想:
第1趟,从第1个到第n个记录中选择关键码最小的记录与第1
个记录交换;
第2趟,从第2个到第n个记录中选择关键码最小的记录与第2
个记录交换;

第i趟, 从第i个到第n个记录中选择关键码最小的记录与第i个
记录交换;

直到第n-1趟,从最后两个记录中选择较小的记录放置在第n-1
位置。排序结束。
在这里插入图片描述

代码实现

#include<iostream>
using namespace std;

void zj_insertsort(int a[], int n)//直接插入排序
{
	int i, j;
	for (i = 2; i <= n; i++)
	{
		if (a[i] < a[i - 1])
		{
			a[0] = a[i];
			for (j = i - 1; a[0] < a[j]; j--)
				a[j + 1] = a[j];
			a[j + 1] = a[0];
		}
	}
	for (i = 1; i <= n; i++)
		printf("%d ", a[i]);
	printf("\n");
}

void zb_insertsort(int b[], int n)//折半插入排序
{
	int i, j, low, mid, high;
	for (i = 2; i <= n; i++)
	{
		b[0] = b[i];
		low = 1;
		high = i - 1;
		while (low <= high)
		{
			mid = (low + high) / 2;
			if (b[0] >= b[mid])
				low = mid + 1;
			else
				high = mid - 1;
		}
		for (j = i - 1; j >= high + 1; j--)
			b[j + 1] = b[j];
		b[high + 1] = b[0];
	}
	for (i = 1; i <= n; i++)
		printf("%d ", b[i]);
	printf("\n");
}

void bubblesort(int c[], int n)//冒泡排序
{
	int i, j;
	int swap;
	for (i = 1; i < n - 1; i++)
	{
		swap = 0;
		for(j=1;j<=n-i;j++)
			if (c[j] > c[j + 1])
			{
				c[0] = c[j + 1];
				c[j + 1] = c[j];
				c[j] = c[0];
				swap = 1;
			}
		if (swap == 0)
			break;
	}
	for (i = 1; i <= n; i++)
		printf("%d ", c[i]);
	printf("\n");
}

void selectsort(int d[], int n)//选择排序
{
	int min,i,j;
	for (i = 1; i < n; i++)
	{
		min = i;
		for ( j = i + 1; j <= n; j++)
		{
			if (d[j] < d[min])
				min = j;
		}
		if (min != i)
		{
			d[0] = d[min];
			d[min] = d[i];
			d[i] = d[0];
		}
	}
	for (i = 1; i <= n; i++)
		printf("%d ", d[i]);
	printf("\n");
}

int main()
{
	int i;
	int a[100], n;
	cin >> n;
	for (i = 1; i <= n; i++)
		scanf("%d", &a[i]);
	zj_insertsort(a,n);
	zb_insertsort(a, n);
	bubblesort(a, n);
	selectsort(a, n);
	return 0;
}

各种排序算法的比较

在这里插入图片描述

  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

时间邮递员

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

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

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

打赏作者

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

抵扣说明:

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

余额充值