数据结构与算法(快速基础C++版)

数据结构与算法(快速基础C++版)

1. 基本概念

“ 程序 = 数据结构 + 算法 ”
说白了就是:
首先,你要操纵的数据是个什么逻辑关系,比如学生信息管理系统,每个学生信息之间就是1对1的线性关系,只有1个前驱和1个后继,那就可以用线性表
其次,我们要对这个存放学生信息的线性表进行增删改查等操作,就要考虑它选用的存储方式,顺序存储和链式存储。
最后,根据不同的存储方式设计操作,并根据时间复杂度和空间复杂度进行算法操作的性能分析
数据结构通过算法实现操作,算法根据数据结构设计程序
数据结构是一门研究数值计算的程序设计中计算机的操作对象以及它们之间的关系操作的学科
在这里插入图片描述

第1章 绪论

1.1 数据结构的研究内容

通常用计算机解决一个问题的步骤,就是:
1.将具体问题抽象为数学模型;2.设计算法;3.最后进行编程、调试与运行。
那抽象数学模型的实质就是:分析问题、提取操作对象、找出操作对象之间的关系并用数学语言描述出来。
操作对象与操作对象之间的关系就是我们说的:数据结构
随着计算机应用领域的扩展,计算机被越来越多地用于非数值计算。描述非数值计算问题的数学模型不是数值计算的数学方程,而是诸如表、树和图之类的具有逻辑关系的数据。
比如:我们常见的学生教务管理系统。操作对象:每位学生的信息(学号、姓名、性别、籍贯、专业.….)。操作算法:查询、插入、修改、删除等。操作对象之间的关系:线性关系,数据结构:线性数据结构、线性表。
在这里插入图片描述
换句话说:
是指相互之间存在一种或多种特定关系的数据元素的集合
或者说,数据结构是带结构数据元素集合

1.2 基本概念和术语

1.2.1 数据、数据元素、数据项和数据对象

1.数据
是能输入计算机且能被计算机处理的各种符号的集合。是信息的载体,是对客观事物符号化的表示,能够被计算机识别、存储和加工。
包括:
数值型的数据:整数、实数等
非数值型的数据:文字、图像、图形、声音等
2.数据元素
是数据的基本单位,在计算机程序中通常作为一个整体进行考虑和处理。也简称为元素,或称为记录、结点或顶点。
在这里插入图片描述
3.数据项
构成数据元素的不可分割的最小单位。
在这里插入图片描述
关系
数据 > 数据元素 > 数据项
例:学生表 > 个人记录 > 学号、姓名…

4.数据对象
性质相同的数据元素的集合,是数据的一个子集
例如:
整数数据对象是集合N={0,±1,±2,……}
字母字符数据对象是集合C={‘A’,‘B’,…,‘Z’}
学籍表也可看作一个数据对象
区别
数据元素:组成数据的基本单位。与数据的关系:是集合的个体
数据对象:性质相同的数据元素的集合。与数据的关系是:集合的子集

1.2.2 数据结构

数据元素不是孤立存在的,它们之间存在着某种关系,数据元素相互之间的关系称为结构(Structure)。是指相互之间存在一种或多种特定关系的数据元素集合或者说,数据结构是带结构的数据元素的集合
数据结构包括以下三个方面的内容:
1.数据元素之间的逻辑关系,也称为逻辑结构
2.数据元素及其关系在计算机内存中的表示(又称为映像),称为数据的物理结构或数据的存储结构
3.数据的运算和实现,即对数据元素可以施加的操作以及这些操作在相应的存储结构上的实现。
逻辑结构
1.描述数据元素之间的逻辑关系。
2.与数据的存储无关,独立于计算机。
3.是从具体问题抽象出来的数学模型。
划分方法
(1)线性结构
有且仅有一个开始和一个终端结点,并且所有结点都最多只有一个直接前趋和一个直接后继。
例如:线性表、栈、队列、串
(2)非线性结构
一个结点可能有多个直接前趋和直接后继
例如:树、图
划分方式:四类基本逻辑结构
(1)集合结构:结构中的数据元素之间除了同属于一个集合的关系外,无任何其它关系。
(2)线性结构:结构中的数据元素之间存在着一对一的线性关系。
(3)树形结构:结构中的数据元素之间存在着一对多的层次关系。
(4)图状结构或网状结构:结构中的数据元素之间存在着多对多的任意关系。
物理结构(存储结构)
1.数据元素及其关系在计算机存储器中的结构(存储方式)。
2.是数据结构在许算机中的表示。
四种基本的存储结构:
1.顺序存储结构
用一组连续的存储单元依次存储数据元素,数据元素之间的逻辑关系由元素的存储位置来表示。
C语言中用数组来实现顺序存储结构。
在这里插入图片描述

2.链式存储结构
用一组任意的存储单元存储数据元素,数据元素之间的逻辑关系用指针来表示
C语言中用指针来实现顺序存储结构。
在这里插入图片描述
在这里插入图片描述

3.索引存储结构
在存储结点信息的同时,还建立附加的索引表。
4.散列存储结构
根据结点的关键字直接计算出该结点的存储地址。
在这里插入图片描述

逻辑结构与存储结构的关系
1.存储结构是逻辑关系的映象与元素本身的映象。
2.逻辑结构是数据结构的抽象,存储结构是数据结构的实现。
3.两者综合起来建立数据元素之间的结构关系。

1.2.3 数据类型和抽象数据类型

在使用高级程序设计语言编写程序时,必须对程序中出现的每个变量、常量或表达式,明确说明它们所属的数据类型
例如,C语言中:
·提供int,char, float, double等基本数据类型
·数组、结构、共用体、枚举等构造数据类型,还有指针、空(void)类型
·用户也可用typedef自己定义数据类型
一些最基本数据结构可以用数据类型来实现,如数组、字符串等;
而另一些常用的数据结构,如栈、队列、树、图等,不能直接用数据类型来表示。
高级语言中的数据类型明显地或隐含地规定了在程序执行期间变量和表达的所有可能的取值范围,以及在这些数值范围上所允许进行的操作。
例如,C语言中定义变量i为int类型,就表示i是[-min,max]范围的整数,在这个整数集上可以进行+、-、*、%等操作
数据类型的作用
1.约束变量或常量的取值范围
2.约束变量或常量的操作
定义:数据类型是一组性质相同的值的集合以及定义于这个值集合上的一组操作的总称。
数据类型 = 值的集合 + 值集合上的一组操作

抽象数据类型(Abstract Data Type,ADT)
是指一个数学模型以及定义在此数学模型上的一组操作。
在这里插入图片描述

1.由用户定义,从问题抽象出数据模型(逻辑结构)
2.还包括定义在数据模型上的一组抽象运算(相关操作)
3.不考虑计算机内的具体存储结构与运算的具体实现算法

1.2.4 概念小结

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

1.3 算法和算法分析

算法的定义
对特定问题求解方法和步骤的一种描述,它是指令的有限序列。其中每个指令表示一个或多个操作。
在这里插入图片描述
算法的描述
自然语言:英语、中文
在这里插入图片描述

流程图:传统流程图、NS流程图
在这里插入图片描述

伪代码:类语言:类C语言
程序代码:C语言程序、JAVA语言程序…
算法是解决问题的一种方法或一个过程,考虑如何将输入转换成输出,一个问题可以多种算法。
程序是用某种程序设计语言对算法的具体实现。
算法的特性
一个算法必须具备以下五个重要特性:
1.有穷性:一个算法必须总是在执行有穷步之后结束,且每一步都在有穷时间内完成。
2.确定性:算法中的每一条指令必须有确切的含义,没有二义性,在任何条件下,只有唯一的一条执行路径,即对于相同的输入只能得到相同的输出。
3.可行性:算法是可执行的,算法描述的操作可以通过已经实现的基本操作执行有限次来实现。
4.输入:一个算法有零个或多个输入。
5.输出:一个算法有一个或多个输出。
算法设计的要求:
1.正确性(Correctness)
2.可读性(Readability)
3.健壮性(Robustness)
4.高效性(Efficiency)
一个好的算法首先要具备正确性,然后是健壮性,可读性;
主要考虑算法的效率,通过算法的效率高低来评判不同算法的优劣程度。
算法效率以下两个方面来考虑:
1.时间效率:指的是算法所耗费的时间;
算法运行时间 = 一个简单操作所需的时间 × 简单操作次数
所以,我们可假设执行每条语句所需的时间均为单位时间。此时对算法的运行时间的讨论就可转化为讨论该算法中所有语句的执行次数,即频度之和了。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.空间效率:指的是算法执行过程中所耗费的存储空间。
时间效率和空间效率有时候是矛盾的。
在这里插入图片描述
在这里插入图片描述

1.4 总结

设计一个好的算法的过程:
在这里插入图片描述

2. 基本的数据结构

第2章 线性表

2.1 线性表的定义和特点

线性表是具有相同特性的数据元素的一个有限序列
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
同一线性表中的元素必定具有相同特性,数据元素间的关系是线性关系
从以上例子可看出线性表的逻辑特征是:
1.在非空的线性表,有且仅有1个开始结点a1,它没有直接前趋,而仅有一个直接后继az;
2.有且仅有一个终端结点an,它没有直接后继,而仅有1个直接前趋an-1;
3.其余的内部结点ai(2≤i≤n-1)都有且仅有1个直接前趋ai-1和1个直接后继ai+1。

2.2 案例引入

案例2.1 一元多项式的运算

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

案例2.2 稀疏多项式的运算

在这里插入图片描述
在这里插入图片描述顺序存储结构存在问题
1.存储空间分配不灵活,比如两个多项式相加,最少的情况下相加为0,C数组不分配空间;最多的清空下相加为7项,C数组分配7个内存空间。
2.运算的空间复杂度高
采用链式存储结构,可以灵活解决上述问题。
在这里插入图片描述
在这里插入图片描述

案例2.3 图书信息管理系统

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

总结

1.线性表中数据元素的类型可以为简单类型,也可以为复杂类型。
2.许多实际应用问题所涉的基本操作有很大相似性,不应为每个具体应用单
独编写一个程序。
3.从具体应用中抽象出共性的逻辑结构+基本操作(就是抽象数据类型),然后实现其存储结构基本操作

2.3 线性的类型定义

这里的类型就是抽象数据类型数据对象、数据对象之间的关系集合、作用在这个数据对象上的基本操作
在这里插入图片描述

2.3.1 基本操作(一):初始化空表、销毁表、清空表

在这里插入图片描述

2.3.2 基本操作(二):判断表为空、返回表长

在这里插入图片描述

2.3.3 基本操作(三):查找某个元素、查找某个符合条件的元素

在这里插入图片描述

2.3.4 基本操作(四):查找前驱、查找后继

在这里插入图片描述

2.3.5 基本操作(五):插入

在这里插入图片描述

2.3.6 基本操作(六):删除、遍历

在这里插入图片描述

总结

以上所提及的运算是逻辑结构上定义的运算。只要给出这些运算的功能是"做什么",至于"如何做"等实现细节、只有待确定了存储结构之后才考虑。

2.4 线性表的顺序表示和实现

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

2.4.1 多项式的顺序存储结构类型定义

在这里插入图片描述

2.4.2 图书表的顺序存储结构类型定义

在这里插入图片描述

2.4.3 数组定义的补充

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

2.4.4 代码实例
2.4.4.1 线性表的初始化
// 线性表的定义
// 其实就是构造结构体:数组+长度
struct SqList
{
	ElemType* elem;  //顺序线性表的表头
	// 也可以这样定义,但是是数组静态分配,上述是动态的,因为可以使用指针指向数组首地址
	//ElemType elem[MAX_SIZE];  
	int length;      //顺序线性表的长度
};
// 线性表的初始化
bool InitList(SqList& L)
{
	L.elem = new ElemType[MAXSIZE];  // //在堆区开辟内存
	if (!L.elem)
	{
		return false;
	}
	L.length = 0;  //设定空表长度为0
	return 1;
}
2.4.4.2 线性表的销毁
// 线性表的销毁
void DestroyList(SqList& L)
{
	if (L.elem)
	{
		delete L.elem;
	}
}
2.4.4.3 线性表的清空
// 线性表的清空
void CLearList(SqList& L)
{
	L.length = 0;
}
2.4.4.4 线性表的长度
// 线性表的长度
int GetLength(SqList& L)
{
	return L.length;
}
2.4.4.5 判断线性表为空
// 判断线性表是否为空
bool IsEmpty(const SqList& L)
{
	// static_cast<bool>(L.length) 的作用是将 L.length 的值转换为布尔值 (bool)。
	return static_cast<bool>(L.length);
}
2.4.4.6 线性表取值
// 线性表取值
// 随机存取的时间复杂度是:O(1)
bool GetELem(const SqList &L, const size_t i, ElemType &e)
{
    if (i < 1 || i > MAXSIZE)
    {
        cerr<<"out of range"<<endl;
        return false;
    }
    e = L.elem[i-1];
    return true;
}
2.4.4.7 线性表的顺序查找
// 线性表的查找
// 顺序查找的时间复杂度是:O(n)
int LocateList(const SqList& L, const ElemType& e)
{
	for (int i = 0; i < L.length; i++)
	{
		if (L.elem[i] == e)
		{
			return i + 1;  //查找成功,返回下标值
		}
	}

	return 0;  // 查找失败,返回0
}

在这里插入图片描述

2.4.4.8 线性表的插入
// 线性表的插入
// 顺序存储的插入的时间复杂度是:O(n)
bool InsertList(SqList& L, const ElemType& e, const int& i)
{
	if (i < 1 || i > L.length)
	{
		return false;  // i值不合法
	}
	if (L.length == MAXSIZE)
	{
		return false;  // 当前存储空间已满
	}
	// 将位于插入位置之后的元素依次向后挪动一位
	for (int j = L.length - 1; j >= i - 1; j--)
	{
		L.elem[j + 1] = L.elem[j];
	}

	// 插入元素
	L.elem[i - 1] = e;
	// 线性表长度+1
	L.length++;

	return true;
}

在这里插入图片描述

2.4.4.9 线性表的删除
// 线性表的删除
// 顺序存储的删除的时间复杂度是:O(n)
bool EraseList(SqList& L, const int& i)
{
	if (i < 1 || i > L.length)
	{
		return false;  // i值不合法
	}

	// 从要删除的i位置开始遍历
	// 也就是将位于删除位置之后的元素依次向前移一位
	for (int j = i; j < L.length; j++)
	{
		L.elem[j - 1] = L.elem[j];
	}

	// 线性表长度-1
	L.length--;

	return true;
}

在这里插入图片描述

2.4.4.10 总结

(1)利用数据元素的存储位置表示线性表中相邻数据元素之间的前后关系,即线性表的逻辑结构与存储结构一致
(2)在访问线性表时,可以快速地计算出任何一个数据元素的存储地址。因此可以粗略地认为,访问每个元素所花时间相等
这种存取元素的方法被称为随机存取法
时间复杂度
1.查找、插入、删除算法的平均时间复杂度为O(n)
就是求次数,比如查找,就是在第几个位置需要的查找次数的累加和 / 元素个数。
比如插入,就是在第几个位置插入需要移动元素个数的累加和 / n种可能。
比如删除,就是在第几个位置删除需要移动元素个数的累加和 / n种可能。
空间复杂度
2.显然,顺序表操作算法的空间复杂度S(n)=O(1) (没有占用辅助空间)
优点
1.存储密度大(结点本身所占存储量 / 结点结构所占存储量,是1:1)
2.可以随机存取表中任一元素
缺点
1.在插入、删除某一元素时,需要移动大量元素
2.浪费存储空间
3.属于静态存储形式,数据元素的个数能自由扩充

2.5 线性表的链式表示和实现

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
简化操作

首元结点处理:如果链表没有头结点,处理链表的第一个节点(比如插入或删除操作)需要特殊处理,因为没有前驱节点。使用头结点后,第一个节点也有前驱节点(即头结点),这样所有节点的处理方式一致,无需对第一个节点做特殊处理。
统一空表和非空表的处理:

空表处理:没有头结点时,链表为时,头指针为 nullptr,这需要单独检查。使用头结点时,链表的头指针始终指向头结点,无论链表是否为空,操作上都可以统一处理。这避免了对空表和非空表进行不同处理的复杂性。
在这里插入图片描述
链表(链式存储结构)的特点:
(1)结点在存储器中的位置是任意的,即逻辑上相邻的数据元素在物理上不一定相邻
(2)访问时只能通过头指针进入链表并通过每个结点的指针域依次向后顺序扫描其余结点,所以寻找第1个结点和最后1个结点所花费的时间不等
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.5.1 单链表的代码实例
2.5.1.1 单链表的初始化(带头结点的单链表)
// 单向链表的定义
struct Lnode
{
	ElemType data;  //结点的数据域 
	Lnode* next;    //结点的指针域, 因为指针还是指向下一结点,结点类型就是Lnode
};
// typedef 都是常用来创建类型别名的工具。
// 将 Lnode * 这个指针类型定义为 LinkList,使得代码更简洁易读。
typedef Lnode* LinkList;  

// 链表的初始化
bool InitList(LinkList& L)
{
	// LinkList& L = Lnode* L,L其实是Lnode类型的指针
	// 表示头指针
	L = new Lnode;  // 在堆区开辟一个头结点,结点的数据类型为Lnode
	//L->next = NULL;  // 空表,也就是说头结点的指针指向为空
	L->next = nullptr;  // // 使用 C++11 的 nullptr,类型安全
	return 1;
}
2.5.1.2 判断单链表为空
// 判断链表是否为空
bool IsEmpty(const LinkList& L)
{
	if (L->next)
	{
		// 非空
		return false;
	}
	else
	{
		// 为空
		return true;
	}
}
2.5.1.3 单链表的销毁

在这里插入图片描述

// 单链表的销毁
void DestroyList(LinkList& L)
{
	LinkList p;
	while (L)
	{
		// 就是定义1个指针p指向头结点,然后将头指针指向下一个结点,并删除当前p指向的结点
		p = L;
		L = L->next;
		delete p;
	}
}
2.5.1.4 单链表的清空

在这里插入图片描述

// 单链表的清空
void CLearList(LinkList& L)
{
	LinkList p, q;
	p = L->next;
	while (p)  // p非空,表示还没到表尾
	{
		q = p->next;
		delete p;
		p = q;
	}
	L->next = nullptr;  // 头结点指针域为空
}
2.5.1.5 单链表的表长

在这里插入图片描述

// 单链表的长度
int GetLength(LinkList& L)
{
	LinkList p;
	p = L->next;  // 将p指向首元结点 
	int i = 0;  // 计数
	while (p)
	{
		// 遍历单链表,统计结点数
		i++;
		p = p->next;
	}
	return i;
}
知识回顾

在这里插入图片描述

2.5.1.6 单链表的取值

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

// 单链表的取值
bool GetElem(const LinkList& L, const int& i, const ElemType& e)
{
	// 因为逻辑顺序和物理顺序相差1,我们说的取第3个数,3代表是物理顺序。
	// 所以我们定义1个指针指向头结点,并当前节点为1,开始遍历直到i=j停止循环
	LinkList p = L->next; 
	int j = 1; // 计数器
	while (p && i > j)
	{
		p = p->next;
		j++;
	}
	if (!p || j > i) return false; // 第i个元素不存在
	e = p->data;
	return true;
}
2.5.1.7 单链表的查找

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

// 单链表的按值查找,返回L中值为e的数据元素的地址
// 时间复杂度:0(n)
LinkList LocateElem(LinkList& L, ElemType& e)
{
	// 在线性表L中查找值为e的数据元素
	// 找到,则返回L中值为e的数据元素的地址,查找失败返回NULL
	LinkList p = L->next;
	while (p && p->data != e)
	{
		p = p->next;
	}
	return p;
}

// 单链表的按值查找,返回L中值为e的位置序号
int LocateElem(LinkList& L, ElemType& e)
{
	// 返回L中值为e的数据元素的位置序号,查找失败返回
	LinkList p = L->next;
	int j = 1;
	while (p && p->data != e)
	{
		p = p->next;
		j++;
	}
	if (p)
	{
		return j;
	}
	else
	{
		return 0;
	}
}
2.5.1.8 单链表的插入

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

// 单链表的插入
// 时间复杂度:0(1)
bool InsertList(LinkList& L, const int& i, const ElemType& e)
{
	LinkList p = L;
	int j = 0;
	while (p && j < i -1)  // 寻找第i - 1个结点, p指向i - 1结点
	{
		p = p->next;
		j++;
	}
	if (!p || j > i - 1)
	{
		return 0;  // i大于表长+1或者小于1,插入位置非法
	}

	// 生成新结点s,将结点s的数据域置为e
	LinkList s = new Lnode;
	s->data = e;

	// 将结点s插入L中
	s->next = p->next;
	p->next = s;
}
2.5.1.9 单链表的删除第i个结点

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

// 单链表的删除
// 将单链表L中第i个数据元素删除
// 时间复杂度:0(1)
bool EraseList(LinkList& L, const int& i, const ElemType& e)
{
	LinkList p = L, q;
	int j = 0;
	while (p && j < i - 1)  // 寻找第 i 个结点的前驱

	{
		p = p->next;
		j++;
	}
	if (!(p->next) || j > i - 1)
	{
		return 0;  // 删除位置不合理
	}
	q = p->next;  // 临时保存被删结点的地址以备释放
	p->next = q->next; // 改变删除结点前驱结点的指针域
	e = q->data;
	delete q;
	return true;
}
2.5.1.10 单链表头插法

在这里插入图片描述
在这里插入图片描述
头插法是倒序插入,先插入An,再是An-1,直到A1;而尾插法是正序,先插A1,再一直到An。

// 单链表的头插
// n表示结点个数
// 算法时间复杂度:O(n)
void CreatListHead(LinkList& L, int n)
{
	L = new Lnode;
	L->next = nullptr;  // 先建立一个带头结点的单链表

	for (int i = 0; i < n; i++)
	{
		LinkList p = new Lnode;
		cin >> p->data; // 输入元素值
		p->next = L->next;  // 插入到表头
		L->next = p;
	}
}
2.5.1.11 单链表尾插法

在这里插入图片描述

// 单链表的尾插
// 算法时间复杂度:O(n)
void CreatListTail(LinkList& L, int n)
{
	L = new Lnode;
	L->next = nullptr;
	LinkList r = L;  // 尾指针r指向头结点

	for (int i = 0; i < n; i++)
	{
		LinkList p = new Lnode;
		cin >> p->data;  // 生成新结点,输入元素值
		p->next = nullptr;
		r->next = p; // 插入到表尾
		r = p; // r指向新的尾结点
	}
}
总结

1.基本上链表的操作,都是和指针挂钩的,就是额外定义1个指针,因为直接对头指针操作,很容易找不到next元素了。比如销毁,额外的指针从头结点开始;如果是清空,则从首元结点开始;比如,计数,额外的指针从头结点开始。
2.并且如果是插入等操作,先顾后面的结点,如果先链接前面的结点,后面的结点就找不到了。
在这里插入图片描述

2.5.2 循环链表的代码实例

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

2.5.2.1 循环链表的合并

在这里插入图片描述

 // 两个链表的合并
 // 算法时间复杂度:O(1)
LinkList ConnectList(LinkList& Ta, LinkList& Tb)
{
	// 假设Ta、Tb都是非空的单循环链表

	LinkList p = Ta->next;  // ①p存表头结点
	Ta->next = Tb->next->next;  // ②Tb表头连结Ta表尾
	delete Tb->next;  // ③释放Tb表头结点
	Tb->next = p;  // ④修改指针
	return Tb;
}
总结

1.单链表使用头指针,比较方便;而单循环链表中,使用尾指针,比较方便。
2.单链表必须通过头指针逐个遍历访问每个结点,而单循环链表可以通过任意一个结点出发。

2.5.3 双向链表的代码实例

在这里插入图片描述
在这里插入图片描述
如果是双向链表,头结点的prior域和尾结点的next域为;而如果是双向循环链表,头结点的prior域指向尾结点和尾结点的next域指向头结点空表,则都是指向
在这里插入图片描述

2.5.3.1 双向链表的插入

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

// 双向链表的定义
// 首先定义了一个结构体 DuLnode,然后通过 typedef 定义了一个指向该结构体的指针类型 DuLinkList。
// 这里 typedef 的部分只定义了 DuLnode* 的别名 DuLinkList,而 DuLnode 是单独的结构体定义。
typedef struct DuLnode
{
	ElemType data;  //结点的数据域 
	DuLnode* prior, * next;    
}*DuLinkList;


// 双向链表的初始化
void InitList(DuLinkList& L)
{
	L = new DuLnode;  
	L->prior = nullptr;  
	L->next = nullptr;  
}

// 双向链表的第i个位置插入元素
bool InsertList(DuLinkList& L, const int& i, const ElemType& e)
{
	// 在带头结点的双向循环链表L中第i个位置之前插入元素e
	DuLinkList p = L->next;
	int j = 1;
	while (p->next && j < i)  // 移动指针到i处
	{
		p = p->next;
		j++;
	}
	if (j < i || j < 1)
	{
		return 0;  // i大于表长插入位置非法
	}

	//在堆区创建要插入的结点
	DuLinkList s = new DuLnode;
	s->data = e;

	// 重新建立链接关系, 将结点s插入链表中
	s->prior = p->prior;  //第一步:s的前趋等于p的前趋
	p->prior->next = s;  //第二步,用p的前趋结点的next指向插入元素s,更改了第一条链
	s->next = p;        //第三步:s的后继指向p
	p->prior = s;       //第四步:p的前趋改为指向s,更改了第二条链
	return true;

}
2.5.3.2 双向链表的删除

在这里插入图片描述

// 双向链表的删除某个元素
bool ListErase_DuL(DuLinkList& L, const int& i, const ElemType& e)
{
	DuLinkList p = L->next;
	int j = 1;
	while (p && j < i)  // 寻找第i个结点,并令p指向其前驱
	{
		p = p->next;
		j++;
	}
	if (j < i || j < 1)
	{
		return 0;  // i大于表长插入位置非法
	}
	p->prior->next = p->next;
	p->next->prior = p->prior;
	delete p;
	return true;
}

2.6 单链表、循环链表、双向链表的比较

在这里插入图片描述

2.7 顺序表和链表的比较

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

2.8 线性表的应用

2.8.1 线性表的合并

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

// 线性表的合并
// 通用算法:顺序表和链表都可以
void Union(LinkList& Ta, LinkList Tb)
{

	La_len = ListLength(Ta);
	Lb_len = ListLength(Tb);
	for (int i = 1; i <= Lb_len; i++)
	{
		GetElem(Lb, i, e);
		if (!Locate(Ta, e))
		{
			ListInsert(&Ta, ++La_len, e);
		}
	}
}
2.8.2 有序表的合并(顺序表实现)

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

// 用顺序存储结构合并两个有序表
void MergeList(const SqList& list1, const SqList& list2, SqList& list3)
{
	int* pa = list1.elem;
	int* pa_last = list1.elem + list1.length - 1;

	int* pb = list2.elem;
	int* pb_last = list2.elem + list2.length - 1;

	list3.length = list1.length + list2.length;
	list3.elem = new int[list3.length];
	int* pc = list3.elem;

	while (pa <= pa_last && pb <= pb_last)  // 两个表都非空
	{
		// 依次“摘取”两表中值较小的结点
		if (*pa <= *pb)
		{
			*pc = *pa;
			pa++;
			pc++;
		}
		else
		{
			*pc = *pb;
			pb++;
			pc++;
		}
	}

	// pb表已到达表尾,将pa中剩余元素加入pc
	while (pa <= pa_last)  
	{ 
		*pc = *pa;
		pa++;
		pc++;
	}
	// pa表已到达表尾,将pb中剩余元素加入pc
	while (pb <= pb_last)
	{
		*pc = *pb;
		pb++;
		pc++;
	}
}
2.8.3 有序表的合并(链表实现)

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

// 用链式存储结构合并两个有序表
// 时间复杂度是:O(n)
// 空间复杂度是:O(1)
void MergeList(const LinkList& La, const LinkList& Lb, LinkList& Lc)
{
	LinkList pa = La->next;
	LinkList pb = Lb->next;
	Lc = La;
	// 用La的头结点作为Lc的头结点
	LinkList pc = Lc;
	while (pa && pb)
	{
		if (pa->data <= pb->data)
		{
			pc->next = pa;
			pc = pa;
			pa = pa->next;
		}
		else
		{
			{
				pc->next = pb;
				pc = pb;
				pb = pb->next;
			}
		}
		pc->next = pa ? pa : pb;
		delete pb;
	}
}

2.9 案例分析与实现

2.9.1 一元多项式的运算

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

2.9.2 稀疏多项式的运算

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

// 存放多项式的单链表的定义
struct PNode
{
	float coef;  //数据域:系数 
	int expn;    //数据域:指数 
	PNode* next;    //指针域
}; 
typedef PNode* Polynomial;

// 多项式创建
void CreatePolyn(Polynomial& p, int n)
{
	// 建立—个带头结点的单链表
	p = new PNode;  
	p->next = nullptr;

	for (int i = 0; i < n; i++)
	{
		Polynomial s = new PNode;  // 生成新结点
		cin >> s->coef >> s->expn; // 输入系数和指数

		Polynomial pre = p;
		Polynomial q = p->next;
		while (q && q->expn < s->expn)
		{
			pre = q;
			q = q->next;
		}
		s->next = q;
		pre->next = s;


	}

}

// 稀疏多项式的相加
void Merge(Polynomial& La, Polynomial& Lb, Polynomial& Lc)
{
	Polynomial pa = La->next;
	Polynomial pb = Lb->next;

	Lc = La;
	Polynomial pc = Lc;  // pc指向多项式的当前结点,初值为pa的头结点
	while (pa && pb)
	{
		// 指数相同的情况下
		if (pa ->expn == pb->expn)
		{
			// 系数相加
			pa->coef += pb->coef;
			pc->next = pa;
			// pa、pb、pc指针均向后移
			pa = pa->next;
			pb = pb->next;
			pc = pc->next;
		}
		// pa小,pc与pa连接
		else if (pa->expn < pb->expn)
		{
			pc->next = pa;
			// pa、pc指针均向后移
			pa = pa->next;
			pc = pc->next;
		}
		// pb小,pc与pb连接
		else if (pa->expn > pb->expn)
		{
			pc->next = pb;
			// pb、pc指针均向后移
			pb = pb->next;
			pc = pc->next;
		}
	}
	// 将pa 或者pb中余下的接到pc后
	pc->next = (pa ? pa : pb);
	delete Lb;

}
2.9.3 图书信息管理系统

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

第3章 栈和队列

3.1 栈和队列的定义和特点

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

3.1.1 栈的定义和特点

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

3.1.2 队列的定义和特点

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

3.2 案例引入

3.2.1 进制转换

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

3.2.2 括号匹配的检验

在这里插入图片描述

3.2.3 表达式求值

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

3.2.4 舞伴问题

在这里插入图片描述

3.3 栈的顺序表示和操作的实现

3.3.1 栈的抽象数据类型的定义

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

3.3.2 顺序栈的表示和实现

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

3.3.2 代码实例
3.3.2.1 栈的初始化

在这里插入图片描述

// 顺序栈的定义
struct SqStack
{
	ElemType* base;  // 栈底指针
	ElemType* top;  // 栈顶指针
	int stacksize;  // 栈可用最大容量
};

// 顺序栈的初始化
bool InitStack(SqStack& S)  // 构造一个空栈
{
	S.base = new ElemType[MAXSIZE];
	if (!S.base)
	{
		return false;
	}
	//栈顶指针等于栈底指针
	S.top = S.base;
	S.stacksize = MAXSIZE;
}
3.3.2.2 判断栈是否为空

在这里插入图片描述

// 判断顺序栈是否为空
bool IsEmpty(SqStack& S)
{
	if (S.base == S.top)
	{
		return true;
	}
	else
	{
		return false;
	}
}
3.3.2.3 求顺序栈的长度

在这里插入图片描述

// 求顺序栈的长度
int StackLength(SqStack& S)
{
	return static_cast<int>(S.top - S.base);
}
3.3.2.4 清空栈

在这里插入图片描述

// 清空栈
bool ClearStack(SqStack& S)
{
	if (S.base)
	{
		S.base == S.top;
		return true;
	}
	else
	{
		return false;
	}
}
3.3.2.5 销毁栈

在这里插入图片描述

// 销毁顺序栈
bool DestoyStack(SqStack& S)
{
	if (S.base)
	{
		delete S.base;
		S.base = S.top = nullptr;
		S.stacksize = 0;
		return true;
	}
}
3.3.2.6 顺序栈的入栈

在这里插入图片描述

// 入栈
bool Push(SqStack& S, ElemType& e)
{
	// (1)判断是否栈满, 若满则出错(上溢)
	if (static_cast<int>(S.top-S.base) == S.stacksize)
	{
		return false;
	}
	// (2)元素e压入栈顶
	*S.top = e;
	// (3)栈顶指针加1
	S.top++;
}
3.3.2.7 顺序栈的出栈

在这里插入图片描述

// 出栈
bool Pop(SqStack& S, ElemType& e)
{
	// (1)判断是否栈空,若空则出错(下溢)
	if (S.base == S.top)
	{
		return false;
	}
	// (2)栈顶指针减1
	S.top--;
	// (3)获取栈顶元素
	e = *S.top;
}

3.4 链栈的表示与实现

在这里插入图片描述

3.4.1 代码实例
3.4.1.1 链栈的初始化

在这里插入图片描述

// 链栈的定义
typedef struct StackNode
{
	ElemType data;  // 数据域
	StackNode* next;  // 栈顶指针
}*LinkStack;

// 链栈的初始化
bool InitStack(LinkStack& S)  // 构造一个空栈
{
	// 这里链栈没有使用链表的头结点,是因为这里会更麻烦
	S = nullptr;
	return true;
}
3.4.1.2 判断链栈是否为空

在这里插入图片描述

// 判断链栈是否为空
bool IsEmpty(LinkStack& S)
{
	if (S==nullptr)
	{
		return true;
	}
	else
	{
		return false;
	}
}
3.4.1.3 链栈的入栈

在这里插入图片描述

// 入栈
bool Push(LinkStack& S, ElemType& e)
{
	LinkStack P = new StackNode;  // 生成新结点P
	P->data = e;  // 将新结点数据域设置为e

	P->next = S;  // 将新结点插入栈顶
	S = P;  // 修改栈顶指针
	return true;
}
3.4.1.4 链栈的出栈

在这里插入图片描述

// 出栈
bool Pop(LinkStack& S, ElemType& e)
{
	if (S==nullptr)
	{
		return false;
	}
	e = S->data;

	LinkStack P = S;
	S = S->next;
	delete P;
	return true;
}
3.4.1.5 取栈顶元素

在这里插入图片描述

// 获取栈顶元素
ElemType GetTop(LinkStack& S)
{
	if (S != nullptr)
	{
		return S->data;
	}
}

3.5 栈与递归

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

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

3.6 队列的顺序表示和操作的实现

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

3.6.1 队列的抽象数据类型的定义

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

3.6.2 队列的顺序表示和实现

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

3.6.3 代码实例
3.6.3.1 循环队列的初始化
// 队列的定义
typedef struct SqQueue
{
	ElemType* base;  // 初始化的动态分配存储空间
	int front;  // 头指针,若队列不空,指向队列头元素
	int rear;  // 尾指针,若队列不空,指向队列尾元素的下一个位置
};

// 队列的初始化
bool InitQueue(SqQueue& Q)  // 构造一个空栈
{
	Q.base = new ElemType[MAXSIZE];  // 分配数组空间
	if (!Q.base)
	{
		return false;  // 分配失败
	}

	// 头指针尾指针置为0,队列为空
	Q.front = 0;
	Q.rear = 0;
}
3.6.3.2 求循环队列的长度

在这里插入图片描述

// 求队列的长度
int GetLength(SqQueue& Q)
{
	return (Q.rear - Q.base + MAXSIZE) % MAXSIZE;
}
3.6.3.3 入队

在这里插入图片描述

// 循环队列入队
bool EnQueue(SqQueue& Q, ElemType& e)
{
	if ((Q.rear + 1) % MAXSIZE == Q.front)
	{
		return false;  // 队满
	}
	Q.base[Q.rear] = e;  // 新元素加入队尾
	Q.rear = (Q.rear + 1) % MAXSIZE;  // 队尾指针+1
	return true;
}
3.6.3.4 出队
// 循环队列出队
bool DeQueue(SqQueue& Q, ElemType& e)
{
	if (Q.rear == Q.front)
	{
		return false;  // 队空
	}
	Q.base[Q.front] = e;  // 保存队头元素
	Q.front = (Q.front + 1) % MAXSIZE;  // 队头 指针+1
	return true;
}
3.6.3.5 取队头元素
// 取队头元素
ElemType GetHead(SqQueue& Q)
{
	if (Q.front != Q.rear)  // 队列不为空
	{
		return Q.base[Q.front];
	}
}

3.7 队列的链式表示和实现

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

3.7.1 代码实例
3.7.1.1 链队的初始化

在这里插入图片描述

// 链队的初始化
bool InitQueue(LinkQueue& Q)  
{
	Q.front = Q.rear = new Qnode;
	Q.front->next = nullptr;
	return true;
}
3.7.1.2 链队的销毁

在这里插入图片描述

// 链队的销毁
bool DestroyQueue(LinkQueue& Q)
{
	while (Q.front)
	{
		Qnode* p = Q.front->next;
		delete Q.front;
		Q.front = p;
	}
		return true;
}
3.7.1.3 链队的入队

在这里插入图片描述

// 入队
bool EnQueue(LinkQueue& Q, const ELemType& e)
{
	//把元素插在最后面
	Qnode* p = new Qnode;
	p->data = e;
	p->next = nullptr;
	Q.rear->next = p;
	Q.rear = p;
	return true;
}
3.7.1.4 链队的出队

在这里插入图片描述

// 出队
bool DeQueue(LinkQueue& Q, const ELemType& e)
{
	if (Q.front == Q.rear)  // 队空
	{
		return false;
	}
	Qnode* p = Q.front->next;
	e = p->data;
	Q.front->next = p->next;
	delete p;
}
// 出队
bool DeQueue(LinkQueue& Q, const ELemType& e)
{
	if (Q.front == Q.rear)  // 队空
	{
		return false;
	}
	Qnode* p = Q.front->next;
	e = p->data;
	Q.front->next = p->next;

	if (Q.rear == p)
	{
		// 
		Q.rear = Q.front;
	}
	delete p;
}
3.7.1.5 求链队的队头元素

在这里插入图片描述

// 求链队的队头元素
bool GetHead(LinkQueue& Q, const ELemType& e)
{
	if (Q.front == Q.rear)  // 队空
	{
		return false;
	}
	e = Q.front->next->data;
	return true;
}

第4章 串、数组和广义表

在这里插入图片描述

4.1 串的定义

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

4.2 案例引入

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

4.3 串的类型定义、存储结构及其运算

4.3.1 串的类型定义

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

4.3.2 串的顺序存储结构

在这里插入图片描述

4.3.3 串的链式存储结构

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

4.3.4 串的模式匹配算法及代码实例

在这里插入图片描述

4.3.4.1 BF算法

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

4.3.4.2 BF算法代码实例
#define MAXLEN 25

// 串的顺序存储定义
struct SString
{
	char ch[MAXLEN];  // 存储串的一维数组
	int length;  // 串的当前长度
};

int index_BF(SString S, SString T)
{
	// 如果不是从第一个字符开始匹配,要从指定的位置开始查找,将i参数化  -->  int i = pos;
	int i = 1, j = 1;
	while (i <= S.length && j <= T.length)
	{
		//  主串和子串当前字符相等
		if (S.ch[i] == T.ch[j])
		{
			// 依次匹配下一个字符
			i++;
			j++;
		}

		// 主串、子串指针回溯重新开始下-次匹配
		else
		{
			// 主串回到下一个字符
			i = i - j + 2;
			// 子串回到第一个字符等到下一次匹配
			j = 1;
		}
	}

	if (j >= T.length)
	{
		return i - T.length;  // 返回匹配的第一个字符的下标
	}
	else
	{
		return 0;  // 模式匹配不成功
	}
}
4.3.4.3 BF算法的时间复杂度

在这里插入图片描述

4.3.4.4 KMP算法

在这里插入图片描述

理解
k-1就是比较元素的个数,末尾是从j-1开始,前端是首元素1开始。比如,有2个公共前后缀,那k - 1 = 2,k = 3。找不到相等的公共前后缀,就是其他情况,k = 1。
在这里插入图片描述

// KMP算法
int index_BF(SString S, SString T)
{
	// 如果不是从第一个字符开始匹配,要从指定的位置开始查找,将i参数化  -->  int i = pos;
	int i = 1, j = 1;
	while (i <= S.length && j <= T.length)
	{
		if (j == 0 || S.ch[i] == T.ch[j])
		{
			// 依次匹配下一个字符
			i++;
			j++;
		}

		// 主串、子串指针回溯重新开始下-次匹配
		else
		{
			j = next[j];  // i不变,j后退
		}
	}

	if (j >= T.length)
	{
		return i - T.length;  // 返回匹配的第一个字符的下标
	}
	else
	{
		return 0;  // 模式匹配不成功
	}
}

在这里插入图片描述

4.4 数组

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

4.4.1 数组的抽象数据类型和定义

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

4.4.2 数组的顺序存储

数组的结构固定,所以基本上不用链式存储,因此一般也不做插入删除的操作。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

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

4.4.3 特殊矩阵的压缩存储

在这里插入图片描述

4.4.3.1 对称矩阵

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

4.4.3.2 三角矩阵

在这里插入图片描述

4.4.3.3 对角矩阵(带状矩阵)

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

4.4.3.4 稀疏矩阵

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

在这里插入图片描述

4.5 广义表

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

4.6 案例分析与实现

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

第5章 树

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

5.1 树和二叉树的定义

5.1.1 树的定义

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

5.1.2 树的基本术语

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

5.1.3 二叉树的定义

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

5.2 案例引入

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

5.3 数和二叉树的抽象数据类型定义

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

5.4 二叉树的性质和存储结构

5.4.1 二叉树的性质

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

5.4.1.1 满二叉树

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

5.4.1.2 完全二叉树

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

5.4.2 二叉树的存储结构

在这里插入图片描述

5.4.2.1 二叉树的顺序存储结构

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

5.4.2.2 二叉树的链式存储结构

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

5.5 遍历二叉树和线索二叉树

5.5.1 遍历二叉树的算法描述

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

5.5.1.1 先序遍历二叉树的操作定义

在这里插入图片描述

5.5.1.2 中序遍历二叉树的操作定义

在这里插入图片描述

5.5.1.3 后序遍历二叉树的操作定义

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

5.5.2 遍历二叉树的递归代码实例
5.5.2.1 二叉树先序遍历

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

// 二叉树的链式定义
typedef struct BiNode
{
	ElemType data;
	BiNode* lchild, * rchild;  // 左、右孩子指针
}BiNode, *BiTree;

// 二叉树的先序遍历 DLR
bool PreOrderTraverse(BiTree& T)
{
	if (T == nullptr)
	{
		return false;  // 空树
	}
	// 首先访问根节点
	visit(T);  // 比如cout<< T->data <<endl;
	//递归遍历左孩子
	PreOrderTraverse(T->lchild);
	//递归遍历右孩子
	PreOrderTraverse(T->rchild);
	return true;
}

在这里插入图片描述

5.5.2.2 二叉树中序遍历

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

// 二叉树的中序遍历 LDR
bool InOrderTraverse(BiTree& T)
{
	if (T == nullptr)
	{
		return false;  // 空树
	}
	
	//递归遍历左孩子
	InOrderTraverse(T->lchild);

	// 访问根节点
	visit(T);

	//递归遍历右孩子
	InOrderTraverse(T->rchild);
	return true;
}
5.5.2.3 二叉树后序遍历

在这里插入图片描述

// 二叉树的后序遍历 LDR
bool PostOrderTraverse(BiTree& T)
{
	if (T == nullptr)
	{
		return false;  // 空树
	}

	//递归遍历左孩子
	PostOrderTraverse(T->lchild);

	//递归遍历右孩子
	PostOrderTraverse(T->rchild);

	// 访问根节点
	visit(T);
	return true;
}
5.5.2.4 遍历算法的分析

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

5.5.3 遍历二叉树的非递归代码实例

在这里插入图片描述
中序遍历为例,因为根节点在最先,而中序先访问左子树,所以根节点入栈,访问完左子树,再将根节点出栈,最后再访问右子树。
在这里插入图片描述

// 顺序栈的定义
struct SqStack
{
	ElemType* base;  // 栈底指针
	ElemType* top;  // 栈顶指针
	int stacksize;  // 栈可用最大容量
};

// 顺序栈的初始化
bool InitStack(SqStack& S)  // 构造一个空栈
{
	S.base = new ElemType[MAXSIZE];
	if (!S.base)
	{
		return false;
	}
	//栈顶指针等于栈底指针
	S.top = S.base;
	S.stacksize = MAXSIZE;
}

// 判断顺序栈是否为空
bool IsEmpty(SqStack& S)
{
	if (S.base == S.top)
	{
		return true;
	}
	else
	{
		return false;
	}
}
// 入栈
bool Push(SqStack& S, ElemType& e)
{
	// (1)判断是否栈满, 若满则出错(上溢)
	if (static_cast<int>(S.top - S.base) == S.stacksize)
	{
		return false;
	}
	// (2)元素e压入栈顶
	*S.top = e;
	// (3)栈顶指针加1
	S.top++;
}

// 出栈
bool Pop(SqStack& S, ElemType& e)
{
	// (1)判断是否栈空,若空则出错(下溢)
	if (S.base == S.top)
	{
		return false;
	}
	// (2)栈顶指针减1
	S.top--;
	// (3)获取栈顶元素
	e = *S.top;
}

// 二叉树的中序遍历——非递归算法
// 以“中序遍历”为例,因为根节点在最先,
// 而中序先访问左子树,所以根节点`入栈`,访问完左子树,再将根节点出栈,最后再访问右子树。
bool InOrderTraverse(BiTree& T)
{
	//第一步:创建一个栈,用于保存二叉树的结点
	SqStack S;
	InitStack(S);

	BiTree p = T;  // p也指向根节点

	// 首先判断是根节点不为空的话,将根节点入栈
	while (p || !IsEmpty(S))
	{
		if (p)
		{
			Push(S, p);
			// 根节点入栈后,访问左子树
			p = p->lchild;
		}
		else
		{
			Pop(S, q);
			visit(q);
			p = p->rchild;
		}
	}
	return true;
}
5.5.4 二叉树的层次遍历代码实例

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

// 队列的定义
typedef struct SqQueue
{
	ElemType* base;  // 初始化的动态分配存储空间
	int front;  // 头指针,若队列不空,指向队列头元素
	int rear;  // 尾指针,若队列不空,指向队列尾元素的下一个位置
};

// 队列的初始化
bool InitQueue(SqQueue& Q)  // 构造一个空栈
{
	Q.base = new ElemType[MAXSIZE];  // 分配数组空间
	if (!Q.base)
	{
		return false;  // 分配失败
	}

	// 头指针尾指针置为0,队列为空
	Q.front = 0;
	Q.rear = 0;
}


// 循环队列入队
bool EnQueue(SqQueue& Q, ElemType& e)
{
	if ((Q.rear + 1) % MAXSIZE == Q.front)
	{
		return false;  // 队满
	}
	Q.base[Q.rear] = e;  // 新元素加入队尾
	Q.rear = (Q.rear + 1) % MAXSIZE;  // 队尾指针+1
	return true;
}

// 循环队列出队
bool DeQueue(SqQueue& Q, ElemType& e)
{
	if (Q.rear == Q.front)
	{
		return false;  // 队空
	}
	Q.base[Q.front] = e;  // 保存队头元素
	Q.front = (Q.front + 1) % MAXSIZE;  // 队头 指针+1
	return true;

}


// 二叉树的层次遍历
bool InOrderTraverse(BiTree& T)
{
	SqQueue Q;
	InitQueue(Q);

	EnQueue(Q, T); // 将根节点入队
	BiTree p;
	while (!QueueEmpty(Q)) // 队不为空,则循环
	{
		// 将根节点出队
		DeQueue(Q, p);
		//访问根结点
		cout << p->data << endl;
		
		// 有左孩子时将其讲队
		if (p->lchild != nullptr)
		{
			EnQueue(Q, p->lchild);
		}
		// 有右孩子时将其讲队
		if (p->rchild != nullptr)
		{
			EnQueue(Q, p->rchild);
		}
	}

}

// 二叉树的后序遍历 LDR
bool PostOrderTraverse(BiTree& T)
{
	if (T == nullptr)
	{
		return false;  // 空树
	}

	//递归遍历左孩子
	PostOrderTraverse(T->lchild);

	//递归遍历右孩子
	PostOrderTraverse(T->rchild);

	// 访问根节点
	visit(T);
	return true;
}
5.5.5 二叉树遍历算法的应用—二叉树的建立

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

// 二叉树的链式定义
typedef struct BiNode
{
	ElemType data;
	BiNode* lchild, * rchild;  // 左、右孩子指针
}BiNode, * BiTree;

// 二叉树的建立(DLR先序遍历,递归算法)
bool CreatBiTree(BiTree& T)
{
	ElemType ch;
	cin >> ch;
	if (ch == "#")//建立空结点的标志为 #
	{
		T = nullptr;
	}
	else
	{
		T = new BiNode;  // 开辟内存 分配1个新结点

		T->data = ch;  // 生成根结点,并存放数据域
		CreatBiTree(T->lchild);  // 构造左子树
		CreatBiTree(T->rchild);  // 构造右子树
	}
	return true;
}
5.5.6 二叉树遍历算法的应用—赋值二叉树

在这里插入图片描述

// 二叉树的复制
bool Copy(const BiTree& T, BiTree& NewT)
{
	if (T == nullptr)  // 空树
	{
		return 0;
	}
	else
	{
		NewT = new BiNode;
		NewT->data = T->data;
		Copy(T->lchild, NewT->lchild);
		Copy(T->rchild, NewT->rchild);
	}
}
5.5.7 二叉树遍历算法的应用—计算二叉树的深度

在这里插入图片描述

// 求二叉树的深度
int Depth(BiTree& T)
{
	if (T == nullptr)
	{
		return 0;
	}
	int m = Depth(T->lchild);
	int n = Depth(T->rchild);
	if (m > n )
	{
		return m + 1;
	}
	else
	{
		return n + 1;
	}
}
5.5.8 二叉树遍历算法的应用—计算二叉树的结点总数

在这里插入图片描述

// 求二叉树的结点数
int CountNode(BiTree& T)
{
	if (T == nullptr)
	{
		return 0;
	}
	 //L
	 int m = CountNode(T->lchild);
	 //R
	 int n = CountNode(T->rchild);
	 //
	 return m + n + 1;
	//更加简单的语句
	//return CountNode(T->lchild) + CountNode(T->rchild) + 1;
}
5.5.9 二叉树遍历算法的应用—计算二叉树的叶子 结点总数

在这里插入图片描述

// 求二叉树的叶子结点数
int LeadNode(BiTree& T)
{
	if (T == nullptr)
	{
		return 0;  // 空树返回0
	}
	// 不是空树,那么下面要判断是不是叶子节点
	// 左右子树都为空,那么是叶子结点
	if (T ->lchild == nullptr && T->rchild == nullptr)
	{
		return 1;
	}
	// 不是叶子结点,那么继续往下遍历下面的左右子树
	else
	{
		return LeadNode(T->lchild) + LeadNode(T->rchild);
	}
}

5.6 线索二叉树

在这里插入图片描述
因为,如果是n个结点,会有2n个指针域,而有n-1个孩子,也就是2n个指针域中,有n-1个用来指示结点的左右孩子,其余n+1个指针域都为
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述


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

5.7 树和森林

在这里插入图片描述

5.7.1 树的存储结构
5.7.1.1 双亲表示法

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

5.7.1.2 孩子链表

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

5.7.1.3 孩子兄弟表示法

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

5.7.2 树与二叉树的转换

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

5.7.3 森林与二叉树的转换

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

5.7.4 树与森林的遍历
5.7.4.1 树的遍历

在这里插入图片描述

5.7.4.2 森林的遍历

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

5.8 哈夫曼树及其应用

5.8.1 哈夫曼树的基本概念

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

5.8.2 哈夫曼树的构造算法

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

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

5.8.3 总结

在这里插入图片描述

5.8.4 代码实例

在这里插入图片描述

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

// 哈夫曼树的定义
typedef struct HNode
{
	int weight;                  //权重
	int parent, lchild, rchild;  //每个结点的双亲、左右孩子的数组下标
} *HuffmanTree;

// 哈夫曼树的初始化
void InitHTree(HuffmanTree& HT, const int n)
{
	if (n <= 1)
	{
		return;
	}

	int m = 2*n - 1;  // 数组共2n-1个元素
	HT = new HNode[m + 1];  // 0号单元未用,HT[m]表示根结点
	// 将2n-1个元素的lch、rch、parent置为0
	for (int i = 1; i <= m; i++)
	{
		HT[i].parent = 0;
		HT[i].lchild = 0;
		HT[i].rchild = 0;
	}

	// 输入前n个元素的weight值
	for (int i = 1; i <= n; i++)
	{
		cin >> HT[i].weight;
	}

}

// 哈夫曼树的构造算法
void CreatHuffman(HuffmanTree& HT, const int n)
{
	// 初始化
	InitHTree(HT, n);

	// 合并产生n-1个结点——构造Huffman树
	for (int i = n + 1; i <= 2 * n - 1; i++)
	{
		int s1 = 0, s2 = 0;
		// 在HT[k](1≤k≤i-1)中选择两个其双亲域为0,
		// 且权值最小的结点, 并返回它们在HT中的序号s1和s2
		Select(HT, i - 1, s1, s2);//重点是这个Select算法
		// 表示从F中删除s1,s2
		HT[s1].parent = i;
		HT[s2].parent = i;
		// s1,s2分别作为i的左右孩子
		HT[i].lchild = s1;
		HT[i].rchild = s2;
		// i的权值为左右孩子权值之和
		HT[i].weight = HT[s1].weight + HT[s2].weight;
	}
}
5.8.5 哈夫曼编码

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

在这里插入图片描述

5.8.6 哈夫曼编码的代码实例

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

5.8.7 哈夫曼编码-解码

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

在这里插入图片描述

第6章 图

在这里插入图片描述

6.1 图的定义和基本术语

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

6.2 案例引入

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

6.3 图的类型定义

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

6.4 图的存储结构

在这里插入图片描述

6.4.1 邻接矩阵
6.4.1.1 邻接矩阵表示

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

6.4.1.2 无向网的邻接矩阵代码实例

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

#define MAXSIZE 100
#define MAXINT 32767
// 图的定义
// 邻接矩阵表示法
struct AMGraph
{
	char Vexs[MAXSIZE];  // 图的顶点向量
	int Arcs[MAXSIZE][MAXSIZE];  // 图的邻接矩阵
	int vexnum, arcnum;  // 图的总顶点数和总边数
};

// 邻接矩阵的LocateVex函数
int LocateVex(AMGraph& G, const char& e)
{
	for (int i = 0; i < G.vexnum; ++i)
	{
		if (G.Vexs[i] == e)
			return i;
	}
	return -1;
}


// 创建无向网
bool CreatUDN(AMGraph& G)
{
	cin >> G.vexnum >> G.arcnum; //输入总顶点数和总边数
	for (int i = 0; i < G.vexnum; i++)
	{
		cin >> G.Vexs[i];  // 依次输入点的信息 
	}

	// 初始化邻接矩阵
	for (int i = 0; i < G.vexnum; i++)
	{
		for (int j = 0; j < G.vexnum; j++)
		{
			G.Arcs[i][j] = MAXINT;  // 边的权值均置为极大值
		}
	}

	// 构造邻接矩阵
	for (int k= 0; k < G.arcnum; k++)
	{
		int i, j, weight;
		char v1, v2;
		cin >> v1 >> v1 >> weight;   // 输入一条边所依附的顶点及边的权值
		//由输入的顶点v1和v2查找到对应的下标i,j
		i = LocateVex(G, v1);
		j = LocateVex(G, v2);
		G.Arcs[i][j] = weight;
		G.Arcs[j][i] = weight;
	}
	return true;
}

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

6.4.2 邻接表

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

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

6.4.3 邻接表的代码实例
// 邻接表表示法


//顶点的结点结构的定义
typedef struct VNode
{
	VecTexType data;  // 数据域,存放顶点
	ArcNode* firstarc;  //指针域,用于保存邻接表的
}VNode, AdjList[MAXSIZE];

//弧(边)的结点结构的定义
struct ArcNode  // 边结点
{
	int adjvex;       // 该边所指向的顶点的位置
	ArcNode* nextarc; //指向下一个边结点
};


//图的结构的定义
struct ALGraph
{
	//定义一个数组,保存图的顶点
	AdjList vertics;
	//定义两个变量,保存当前图的顶点个数以及边的条数
	int vexnum, arcnum;
};


// 邻接表的LocateVex函数
int LocateVex(ALGraph& G, const char& v)
{
	for (int i = 0; i < G.vexnum; ++i)
	{
		if (G.vertics[i].data == v)
			return i;
	}
	return -1;
}

// 创建无向图算法
bool CreatUDG(ALGraph& G)
{
	cin >> G.vexnum >> G.arcnum;  // 输入总顶点数,总边数

	// 输入各点,构造表头结点表
	for (int i = 0; i < G.vexnum; i++)
	{
		cin >> G.vertics[i].data;  // 输入结点值
		G.vertics[i].firstarc == nullptr;  // 初始化表头结点的指针域
	}

	// 输入各边,构造邻接表
	for (int k = 0; k < G.arcnum; k++)
	{
		// 输入一条边依附的两个顶点
		char v1, v2;
		cin >> v1 >> v2;
		int i = LocateVex(G, v1);
		int j = LocateVex(G, v2);

		ArcNode* p1 = new ArcNode;  // 生成一个新的边结点*p1
		p1->adjvex = j; // 邻接点序号为j
		p1->nextarc = G.vertics[i].firstarc;
		G.vertics[i].firstarc = p1;  // 将新结点*p1插入顶点v[i]的边表头部

		// 因为是无向图,所以b-->e,也得e-->b
		ArcNode* p2 = new ArcNode;  // 生成另一个对称的新的边结点*p2
		p2->adjvex = i;  // 邻接点序号为i
		p2->nextarc = G.vertics[j].firstarc;
		G.vertics[j].firstarc = p2;  // 将新结点*p2插入顶点v[j]的边表头部
	}
	return true;
}
6.4.4 邻接矩阵和邻接表的关系


在这里插入图片描述

6.4.5 十字链表

在这里插入图片描述

在这里插入图片描述

6.4.6 邻接多重表

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

6.5 图的遍历

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

6.5.1 深度优先遍历

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

在这里插入图片描述

6.5.2 深度优先搜索遍历算法的实现

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

// 邻接矩阵表示法
struct AMGraph
{
	char Vexs[MAXSIZE];  // 图的顶点向量
	int Arcs[MAXSIZE][MAXSIZE];  // 图的邻接矩阵
	int vexnum, arcnum;  // 图的总顶点数和总边数
};

// 定义一个visited数组作标志
int visited[MAXSIZE] = {0};

// 深度优先遍历算法DFS
void DFS(AMGraph& G, int v)
{
	cout << v;  // 输出图顶点的包含的内容
	visited[v] = true;
	for (int w = 0; w < G.vexnum; w++)
	{
		if (!visited[w] && G.Vexs[v][w])
		{
			DFS(G, w);  // //w是v的邻接点,如果w未访问,则递归调用DFS
		}
	}
}

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

6.5.3 广度优先遍历

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

6.5.4 广度优先遍历的代码实例

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

 // 队列的定义
typedef struct SqQueue
{
	ElemType* base;  // 初始化的动态分配存储空间
	int front;  // 头指针,若队列不空,指向队列头元素
	int rear;  // 尾指针,若队列不空,指向队列尾元素的下一个位置
};

// 队列的初始化
bool InitQueue(SqQueue& Q)  // 构造一个空栈
{
	Q.base = new ElemType[MAXSIZE];  // 分配数组空间
	if (!Q.base)
	{
		return false;  // 分配失败
	}

	// 头指针尾指针置为0,队列为空
	Q.front = 0;
	Q.rear = 0;
}

// 循环队列入队
bool EnQueue(SqQueue& Q, ElemType& e)
{
	if ((Q.rear + 1) % MAXSIZE == Q.front)
	{
		return false;  // 队满
	}
	Q.base[Q.rear] = e;  // 新元素加入队尾
	Q.rear = (Q.rear + 1) % MAXSIZE;  // 队尾指针+1
	return true;
}

// 循环队列出队
bool DeQueue(SqQueue& Q, ElemType& e)
{
	if (Q.rear == Q.front)
	{
		return false;  // 队空
	}
	Q.base[Q.front] = e;  // 保存队头元素
	Q.front = (Q.front + 1) % MAXSIZE;  // 队头 指针+1
	return true;
}

int visited[MAXSIZE] = {0};
// 广度优先遍历算法BFS
void BFS(AMGraph& G, int v)
{
	cout << v;
	visited[v] = true;  // 访问第v个顶点
	SqQueue Q;
	InitQueue(Q);  // 辅助队列Q初始化
	EnQueue(Q, v);  // v入队
	while (!(Q.front == Q.rear))  // 队列非空
	{
		// 队头元素出队
		DeQueue(Q, u);
		// 循环遍历u的每个邻接点
		for (int w = FistAdjVex(G,u); w >= 0; w=NextAdjVex(G,u,w))
		{
			// 如果这个邻接点还没访问,就入队
			if (!visited[w])
			{
				visited[w] = true;
				EnQueue(Q, w);  // v入队
			}
		}
	}
}

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

6.6 图的应用

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

6.6.1 生成树

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

6.6.2 构造最小生成树

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

6.6.2.1 普利姆(Prim)算法

在这里插入图片描述

6.6.2.2 克鲁斯卡尔(Kruskal)算法。

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

6.6.3 最短路径

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

在这里插入图片描述

6.6.3.1 Dijistra算法

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

6.6.3.2 Floyd算法

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

6.6.4 有向无环图及其应用

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

6.6.5 关键路径

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

3. 基本的数据处理技术

在这里插入图片描述

第7章 查找技术

7.1 查找的基本概念

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

7.2 线性表的查找

7.2.1 顺序查找(线性查找)

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

7.2.2 折半查找(二分或对分查找)

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

7.2.2.2 折半查找的代码实例

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

7.2.3 分块查找

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

7.3 树表的查找

在这里插入图片描述

7.3.1 二叉排序树

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

7.3.1.1 二叉排序树的存储结构

在这里插入图片描述

7.3.1.2 二叉排序树的代码实例

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

7.3.1.3 二叉排序树的查找分析

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

7.3.1.4 二叉排序树的插入

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

7.3.1.5 二叉排序树的生成

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

7.3.1.6 二叉排序树的删除

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

7.3.2 平衡二叉树

在这里插入图片描述


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

在这里插入图片描述

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

7.4 哈希表的查找

7.4.1 散列表的基本概念

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

7.4.2 散列函数的构造方法

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

7.4.2.1 直接定址法

在这里插入图片描述

7.4.2.2 除留余数法

在这里插入图片描述

7.4.3 处理冲突的方法
7.4.3.1 开放地址法

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

7.4.3.2 链地址法(拉链法)

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

7.4.4 散列表的查找

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

第8章 排序技术

8.1 基本概念和排序方法概述

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

8.2 插入排序

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

8.2.1 直接插入排序

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

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

8.2.2 折半插入排序

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

8.2.3 希尔排序

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

8.3 交换排序

在这里插入图片描述

8.3.1 冒泡排序

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

在这里插入图片描述

8.3.2 快速排序

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

8.4 选择排序

8.5 归并排序

8.6 基数排序

8.7 外部排序

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Andrew_Xzw

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

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

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

打赏作者

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

抵扣说明:

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

余额充值