查找
根据某个关键字k,从集合中找出与关键词相同的记录
根据集合中的记录是否是固定的,可分为静态查找和动态查找,其中动态查找还可发生插入和删除
顺序查找
注意这里的数据存储是从下标1开始的,时间复杂度为O(n)
int Sequentialsearch(List Tb1 ,ElementType K) {
//在data[1]~data[n]中查找关键字为K的元素
int i;
Tb1->Data[0] = K;//哨兵节点
for (i = Tb1->Last; Tb1->Data[i] != K; i--)//从后往前找 如果找到就返回下标 没找到就返回哨兵的下标0
return i;
}
二分查找
n个元素有序连续存放,可进行二分查找,时间复杂度为O(log2n)
例如:
int BinarySearch(List Tbl, ElementType K) {
int left, right, mid;
left = 1;//初始左边界
right = Tbl->Last;//初始右边界
while (left <= right) {
mid = (left + right) / 2;//计算中间元素坐标
if (K < Tbl->Data[mid]) right = mid - 1;//调整右边界
else if (K > Tbl->Data[mid])left = mid + 1;//调整左边界
else return mid;//查找成功返回元素下标
}
return NotFound;//查找不成功返回notfound标记
}
树的定义
树(Tree):n(n≥0)个结点构成的有限集合
当 n=0 时,称为空树
特征
- A为根(Root),其余节点为子树(SubTree)
- 子树不相交
- 除了根节点外,每棵树有且仅有一个父节点
- 一棵n个节点的树有n-1条边
术语
- 结点的度(Degree):结点的子树个数
- 树的度:树的所有结点中最大的度数
- 叶结点(Leaf):度为 0 的结点
- 父结点(Parent):有子树的结点是其子树的根结点的父结点
- 子结点(Child):若 A 结点是 B 结点的父结点,则称 B 结点是 A 结点的子结点,也称孩子结点
- 兄弟结点(Sibling):具有同一父结点的各个结点彼此是兄弟结点
表示方法
兄弟儿子表示法
- Element 存值
- FirstChild 指向第一个子节点
- NextSibling 指向下一个兄弟节点
二叉树
度为2的树,其实是兄弟儿子表示法向右旋转45度的结果
- Element 存值
- Left 指向左子树
- Right 指向右子树
二叉树
定义
二叉树T:一个有穷的结点集合,这个集合可以为空,若不为空则分为左子树TL和右子树TR
二叉树的子树有左右顺序之分
基本形态
特殊形态
- 斜二叉树
- 满二叉树
- 完全二叉树:从满二叉树的右下角叶子往左依次删除节点
性质
- n2:度为2的节点,n1:度为1的节点个数,n0:叶节点个数
- 一个二叉树的节点个数=n2+n1+n0=n22+n11+1
- 一个二叉树第i层的最大结点数为2^(i-1)
- 深度为k(从1开始)的二叉树最大节点数为2^k-1
- 对任何非空二叉树T,叶结点个数n0=度为2的非叶结点个数n2+1
按从上至下、从左到右顺序存储 n 个结点的完全二叉树的结点父子关系:
- 非根结点(序号 i > 1)的父结点的序号是 ⌊i/2⌋(向下取整)
- 结点(序号为 i)的左孩子结点的序号是 2i(若 2 i ≤ n,否则没有左孩子
- 结点(序号为 i)的右孩子结点的序号是 2i+1(若 2 i +1 ≤ n,否则没有右孩子
- 高度(从1开始)最大为log2n +1
操作
Boolean IsEmpty(BinTree BT)
:判别 BT 是否为空void Traversal(BinTree BT)
:遍历,按某顺序访问每个结点BinTree CreatBinTree()
:创建一个二叉树
遍历
void PreOrderTraversal(BinTree BT)
:先序——根、左子树、右子树void InOrderTraversal(BinTree BT)
:中序——左子树、根、右子树void PostOrderTraversal(BinTree BT)
:后序——左子树、右子树、根void LevelOrderTraversal(BinTree BT)
:层次遍历,从上到下、从左到右
顺序存储
链表存储
typedef int Element;
typedef struct TreeNode* BinTree;
struct TreeNode {
Element Data; // 存值
BinTree Left; // 左儿子结点
BinTree Right; // 右儿子结点
};
二叉树的遍历
先中后序遍历经过节点的路线相同,只是访问节点的时机不同
先序遍历
递归实现
- 访问根节点
- 先序遍历左子树
- 先序遍历右子树
void PreOrderTraversal(BinTree BT) {
if (BT) {
printf("%d", BT->Data);//打印根
PreOrderTraversal(BT->Left);//递归左子树
PreOrderTraversal(BT->Right);//递归右子树
}
}
非递归实现
- 遇到节点就访问并压栈,然后遍历左子树
- 左子树遍历结束后从栈顶弹出节点
- 遍历右子树
void PreOrderTraversal(BinTree BT) {
BinTree T = BT;
Stack S = CreateStack(); //创建并初始化堆栈
while (T || !IsEmpty(S)) { //当树不为空或堆栈不空
while (T) {
printf("%d", T->Data); //打印结点
Push(S, T); //压栈
T = T->Left; //遍历左子树
}
if (IsEmpty(S)) { //当堆栈不空
T = Pop(S); //出栈
T = T->Right; //访问右子树
}
}
}
中序遍历
递归实现
- 中序遍历左子树
- 访问根节点
- 中序遍历右子树
void InOrderTraversal(BinTree BT) {
if (BT) {
InOrderTraversal(BT->Left);//递归左子树
printf("%d", BT->Data);//打印根
InOrderTraversal(BT->Right);//递归右子树
}
}
非递归实现
- 遇到节点就把它压栈,并遍历左子树
- 左子树遍历结束后从栈顶弹出节点并访问
- 遍历右子树
void InOrderTraversal(BinTree BT) {
BinTree T = BT;
Stack S = CreateStack(); //创建并初始化堆栈
while (T || !IsEmpty(S)) { //当树不为空或堆栈不空
while (T) {
Push(S, T); //压栈
T = T->Left; //遍历左子树
}
if (IsEmpty(S)) { //当堆栈不空
T = Pop(S); //出栈
printf("%d", T->Data); //打印结点
T = T->Right; //访问右子树
}
}
}
后序遍历
递归实现
- 后序遍历左子树
- 后序遍历右子树
- 访问根节点
void PostOrderTraversal(BinTree BT) {
if (BT) {
PostOrderTraversal(BT->Left);//递归左子树
PostOrderTraversal(BT->Right);//递归右子树
printf("%d", BT->Data);//打印根
}
}
非递归实现
- 遇到节点就压栈,并遍历左子树
- 左子树遍历结束后出栈
- 遍历右子树并把元素保存在数组中
- 逆序输出数组
void PostOrderTraversal(BinTree BT){
BinTree T = BT;
Stack S = CreateStack(); // 创建并初始化堆栈 S
vector<BinTree> v; // 创建存储树结点的动态数组
Push(S,T);
while(!IsEmpty(S)){ // 当树不为空或堆栈不空
T = Pop(S);
v.push_back(T); // 出栈元素进数组
if(T->Left)
Push(S,T->Left);
if(T->Right)
Push(S,T->Right);
}
reverse(v.begin(),v.end()); // 逆转
for(int i=0;i<v.size();i++) // 输出数组元素
printf("%d",v[i]->Data);
}
层序遍历
从上到下从左到右访问所有节点,使用队列将二维结构线性化,以解决访问左儿子后如何访问右儿子的问题:
- 根节点入队
- 队首元素出队
- 访问该元素所在节点
- 若该元素左右孩子非空,则左右孩子依次入队
- 重复2-4,直到队列为空
void LevelOrderTraversal(BinTree BT){
Queue Q=CreatQueue(); // 创建队列
BinTree T;
if(!BT) return;//如果是空树直接返回
AddQ(Q, BT); // BT 入队
while(!Q.empty(Q)){
T = DeleteQ(Q); // 队首元素出队
printf("%d",T->Data); // 访问取出的节点
if(T->Left) // 如果存在左儿子结点
AddQ(T->Left); // 入队
if(T->Right) // 如果存在右儿子结点
AddQ(T->Right);
}
}
应用
输出叶子节点
在二叉树的遍历算法中增加检测节点的左右子树是否都为空
void PreOrderTraversal(BinTree BT) {
if (BT) {
if (!BT->Left && !BT->Right)
printf("%d", BT->Data);//打印根
PreOrderTraversal(BT->Left);//递归左子树
PreOrderTraversal(BT->Right);//递归右子树
}
}
求二叉树的高度
递归求左右子树的深度
int GetHeight(BinTree BT) {
int HL, HR, MaxH;
if (BT) {
HL = GetHeight(BT->Left);//求左子树深度
HR = GetHeight(BT->Right);//求右子树深度
MaxH = (HL > HR) ? HL : HR;//取左右深度的较大值
return MaxH + 1;//返回树的深度
}
else return 0;
}
由两种遍历序列确定二叉树
必须要有中序遍历
- 根据先序(后序)遍历序列第一个(最后一个)结点确定根结点
- 根据根结点在中序序列中分割出左右两个子序列
- 对左子树和右子树分别递归使用同样的方法继续分解
判断两棵二叉树是否同构
- 题目:两棵树如果通过若干次左右孩子互换可以变成同一棵树,则称两棵树同构。如图,上两棵树同构,下两棵树不同构。
- 输入:
- 输出:
Yes
或No
- 思路:
- 首先构造出两棵树,即找出两棵树的根节点
Tree BuildTree
- 根据观察可以知道,输入格式为
值 左子树 右子树
。除了根节点外,每个节点的下标都会作为子树出现一次 - 我们可以每输入一个节点就累加它的下标,并且减去它左右子树的下标
- 直到输入结束,得出的值就是没有被减过的下标,也就是根的下标
- 根据观察可以知道,输入格式为
- 然后从根节点往下依次对比两棵树的左右子树看是否同构
bool Judge
- 若根节点都为空,同构
- 若有一个根节点为空,非同构
- 若根节点数值不同,非同构
- 若左子树都不为空且值相等,分别进入左右两棵子树判断
- 左子树不为空且值不等,或者某一个左子树为空,调换位置分别进入左右子树判断
- 首先构造出两棵树,即找出两棵树的根节点
#include<iostream>
using namespace std;
#define MaxTree 10
#define Null -1
typedef char Element;
typedef int Tree;
struct TreeNode {
Element Data; // 存值
Tree Left; // 左子树下标
Tree Right; // 右子树下标
}T1[MaxTree], T2[MaxTree];//两个数组来存树
Tree BuildTree(struct TreeNode T[]) {//建二叉树 返回根节点下标
int n;
Tree Root = 0;
char cl, cr;//左右子树的下标
cin >> n;
if (!n)
return Null;
for (int i = 0; i < n; i++) {
cin >> T[i].Data >> cl >> cr;
Root += i;//下标进行累加
//对左子树进行处理
if (cl == '-')
T[i].Left = Null;//若左子树为空
else {
T[i].Left = cl - '0';//将字符转换为数字下标并储存到树中
Root -= T[i].Left;//减去左子树的下标
}
//对右子树进行处理
if (cr == '-')
T[i].Right = Null;//若右子树为空
else {
T[i].Right = cr - '0';//将字符转换为数字下标并储存到树中
Root -= T[i].Right;//减去右子树的下标
}
}
return Root;
}
bool Judge(Tree R1, Tree R2) {//判断两棵树是否同构
if ((R1 == Null) && (R2 == Null)) //两棵树都为空,同构
return 1;
if (R1 == Null && R2 != Null || R1 != Null && R2 == Null)//有一棵树为空,非同构
return 0;
if (T1[R1].Data != T2[R2].Data)//根节点数值不同,非同构
return 0;
if ((T1[R1].Left != Null && T2[R2].Left != Null) && (T1[T1[R1].Left].Data == T2[T2[R2].Left].Data))//左子树都不为空且值相等
return Judge(T1[R1].Left, T2[R2].Left) & Judge(T1[R1].Right, T2[R2].Right);//分别进入左右两棵子树判断
else//左子树不为空且值不等,或者某一个左子树为空
return Judge(T1[R1].Right, T2[R2].Left) & Judge(T1[R1].Left, T2[R2].Right);//调换位置分别进入左右子树判断
}
int main() {
Tree R1, R2;
//建二叉树1
R1 = BuildTree(T1);
//建二叉树2
R2 = BuildTree(T2);
//判别是否同构并输出
if (Judge(R1, R2))
printf("Yes\n");
else
printf("No\n");
return 0;
}
Tree Traversals Again
树之习题选讲-Tree Traversals Again①_哔哩哔哩_bilibili
《数据结构》03-树3 Tree Traversals Again-CSDN博客
二叉搜索树
定义
也称二叉搜索树或排序二叉树
- 非空左子树所有值小于其根节点的值
- 非空右子树所有值大于其根节点的值
- 左右子树都是二叉搜索树
操作
BinTree Find(ElementType X,BinTree BST)
:从二叉搜索树 BST 中查找元素 X,返回其所在结点地址BinTree FindMin(BinTree BST)
:从二叉搜索树 BST 中查找并返回最小元素所在结点的地址BinTree FindMax(BinTree BST)
:从二叉搜索树 BST 中查找并返回最大元素所在结点的地址BinTree Insert(ElementType X,BinTree BST)
:插入一个元素进 BSTBinTree Delete(ElementType X,BinTree BST)
:从 BST 中删除一个元素
查找
查找效率取决于树的高度
- 从根节点开始查找,如果树为空返回null
- 若搜索树非空,节点值和X进行比较
- X<根节点,在左子树中继续查找
- X>根节点,在右子树中继续查找
- X=根节点,搜索完成返回指向此节点的指针
typedef int Element;
typedef struct TreeNode* BinTree;
struct TreeNode {
Element Data; // 存值
BinTree Left; // 左儿子结点
BinTree Right; // 右儿子结点
};
BinTree Find(Element X, BinTree BST)
{
if (!BST) // 树为空
return NULL;
if (X < BST->Data)
return Find(X, BST->Left);//在左子树中继续查找
else if (X > BST->Data)
return Find(X, BST->Right); // 在右子树中继续查找
else
return BST;
}
//或者可将尾递归改成执行效率更高的迭代函数
BinTree Find2(Element X, BinTree BST)
{
while (BST)
{
if (X < BST->Data)
BST = BST->Left; // 向左子树中移动,继续查找
else if (X > BST->Data)
BST = BST->Right; // 向右子树中移动,继续查找
else
return BST; // 查找成功,返回找到节点的地址
}
return NULL; // 查找失败
}
最小/最大元素
// 查找最小元素(递归
BinTree FindMin(BinTree BST)
{
if (!BST)
return NULL;
if (!BST->Left)
BST = BST->Left;
else
return BST;
}
// 查找最大元素(迭代
BinTree FindMax(BinTree BST)
{
if (BST)
while (BST->Right)
BST = BST->Right;//向右分支继续查找,直到最右节点
return BST;
}
插入
关键是找到插入元素的位置,与Find方法类似
// 插入
BinTree Insert(Element X, BinTree BST)
{
if (!BST) // 若原树为空,申请空间建一个单节点二叉树
{
BinTree BST = (BinTree)malloc(sizeof(struct TreeNode));
BST->Data = X;
BST->Left = NULL;
BST->Right = NULL;
return BST;
}
else // 查找要插入元素的位置
{
if (X < BST->Data) // 递归插入左子树
BST->Left = (X, BST->Left);
else if (X > BST->Data) // 递归插入右子树
BST->Right = (X, BST->Right);
}
// X已经存在,什么都不做
return BST;
}
删除
有三种情况:
- 要删除的是叶节点:直接删除再修改父结点指针为null
- 要删除的节点只有一个孩子:将父节点指向要删除节点的孩子节点
- 要删除的节点有左右子树:用右子树最小元素或左子树最大元素替代被删除节点
//删除
BinTree Delete(Element X, BinTree BST)
{
BinTree Tmp;
if (!BST)
printf("要删除的元素未找到");
else if (X < BST->Data)
Delete(X, BST->Left); // 左子树递归删除
else if (X > BST->Data)
Delete(X, BST->Right); // 右子树递归删除
else // 找到要删除的节点
{
if (BST->Left && BST->Right) // 被删除节点有左右两个节点
{
Tmp = FindMin(BST->Right); // 右子树中中最小节点替换当前节点
BST->Data = Tmp->Data;
BST->Right = Delete(BST->Data, BST->Right); // 在删除节点的右子树中删除最小节点
}
else // 被删除节点只有一个节点或无节点
{
Tmp = BST;
if (!BST->Left) // 左子树为空
BST = BST->Right;
else if (!BST->Right) // 右子树为空
BST = BST->Left;
free(Tmp);
}
}
return BST;
}
平衡二叉树
定义
- 二叉搜索树的搜索效率与其树的深度相关,搜索节点数插入的不同次序,将导致不同的深度和平均查找长度,因此提出平衡二叉树的概念
- 节点数为nh的平衡二叉树最小节点数为:
- 节点数为n的AVL树最大高度为O(log2n)
调整
插入新节点时,平衡二叉树可能会被破坏,因此要从离插入结点最近的结点调整。
RR单旋
破坏者(BR下面)在被破坏者(A)右子树的右子树上(RR插入)时采用RR旋转调整:把A的右子树B的左子树BL腾出来挂到A的右子树上,把A挂在B的左子树上,返回B作为当前子树的根
AVLTree RRRotation(AVLTree A){//此时根节点是A
AVLTree B = A->right; // B 为 A 的右子树
A->right = B->left; // B 的左子树挂在 A 的右子树上
B->left = A; // A 挂在 B 的左子树上
return B; // 此时 B 为根结点
}
LL单旋
破坏者(BL下面)在被破坏者(A)左子树的左子树上(LL插入)时采用LL旋转调整:把A的左子树B的右子树BR腾出来挂到A的左子树上,把A挂在B的右子树上,返回B作为当前子树的根
AVLTree LLRotation(AVLTree A){
// 此时根节点是 A
AVLTree B = A->left; // B 为 A 的左子树
A->left = B->right; // B 的右子树挂在 A 的左子树上
B->right = A; // A 挂在 B 的右子树上
return B; // 此时 B 为根结点
}
LR双旋
破坏者(CL或CR下面)在被破坏者(A)左子树的右子树上(LR插入)时采用LR旋转调整:先将A的左节点B作为根结点进行RR单旋转化为LL插入,再将A作为根结点进行LL单旋
AVLTree LRRotation(AVLTree A){
// 先 RR 单旋
A->left = RRRotation(A->left);
// 再 LL 单旋
return LLRotation(A);
}
RL双旋
破坏者(CL或CR下面)在被破坏者(A)右子树的左子树上(RL插入)时采用RL旋转调整:先将A的右节点B作为根结点进行LL单旋转化为RR插入,再将A作为根结点进行RR单旋
AVLTree RLRotation(AVLTree A){
// 先 LL 单旋
A->right = LLRotation(A->right);
// 再 RR 单旋
return RRRotation(A);
}
题目
是否同一棵二叉搜索树
《数据结构》04-树4 是否同一棵二叉搜索树_给定一个插入序列就可以唯一确定一棵二叉搜索树。然而,一棵给定的二叉搜索树却可-CSDN博客
Complete Binary Search Tree
树之习题选讲-Complete Binary Search Tree①_哔哩哔哩_bilibili
《数据结构》04-树6 Complete Binary Search Tree-CSDN博客
堆
定义
一种优先队列,取出元素的顺序依照元素的优先权(关键字),使用完全二叉树存储在数组中
有两个特性:
- 结构性:用数组表示的完全二叉树
- 有序性:任一节点的关键字是其字数所有节点的最大值(最大堆MaxHeap)或最小值(最小堆MinHeap)
int maxsize=1001;//最大容量
int maxdata=10000;//哨兵的值
typedef int Element;
typedef struct HeapStruct *MaxHeap;
struct HeapStruct{
Element *data;//存值的数组
int size;//当前元素个数
int capacity;//堆容量
};
操作
MaxHeap Create(int MaxSize)
:创建一个空的最大堆Boolean IsFull(MaxHeap H)
:判断最大堆 H 是否已满Boolean Insert(MaxHeap H,ElementType item)
:将元素 item 插入最大堆 HBoolean IsEmpty(MaxHeap H)
:判断最大堆 H 是否为空ElementType DeleteMax(MaxHeap H)
:取出并删除 H 中最大元素(高优先级)
创建
// 创建容量为maxsize的空的最大堆
MaxHeap Create()
{
MaxHeap H = malloc(sizeof(struct HeapStruct));
H->data = malloc((maxsize + 1) * sizeof(Element));
H->size = 0;
H->capacity = maxsize;
H->data[0] = maxdata; // 哨兵为大于堆中所有可能元素的值,以便后续操作
return H;
}
是否为空/已满
// 是否为空
bool IsEmpty(MaxHeap H)
{
return H->data[1] == NULL;
}
// 是否已满
bool IsFull(MaxHeap H)
{
return H->data[maxsize] != NULL;
}
插入
// 元素item插入最大堆,data[0]已被定义为哨兵
void Insert(MaxHeap H, Element item)
{
int i;
if (IsFull(H))
{
printf("最大堆已满");
return;
}
i = ++H->size; // i指向插入堆后最后一个元素的位置
for (; H->data[i / 2] < item; i /= 2)
{
H->data[i] = H->data[i / 2]; // 向下过滤节点
}
H->data[i] = item; // 插入item
}
删除并返回最大元素
// 取出并删除堆中最大元素
Element DeleteMax(MaxHeap H)
{
int parent, child;
Element max, temp;
if (IsEmpty(H))
{
printf("最大堆为空");
return;
}
max = H->data[1]; // 取出根节点最大值
// 用最大堆中最后一个元素从根节点向下过滤节点节点
temp = H->data[H->size--];
for (parent = 1; parent * 2 <= H->size; parent = child)
{
child = parent * 2;
if ((child != H->size) && (H->data[child] < H->data[child + 1]))
child++; // child指向左右子节点中的较大者
if (temp >= H->data[child])
break; // temp节点找到它的位置,即作为父节点比子节点大
else
H->data[parent] = H->data[child]; // 否则继续向下寻找
}
H->data[parent] = temp;
return max;
}
最大堆的建立
把已经存在的n个元素按最大堆的要求存放在一个一维数组中
- 方法一:通过插入操作,将n个元素一个个相继插入到一个初始为空的堆中去,时间最大代价为nlog2n
- 方法二:将n个元素顺序存入,再从右下角开始调整各节点位置,以满足最大堆的有序特性,是线性时间复杂度
// 方法2调整堆栈使之成为最大堆
void adjust(MaxHeap H)
{
int child, parent, temp;
int childs, parents, temps;
for (parent = H->size / 2; parent > 0; parent--)
{ // 从最后一个父节点向前遍历调整
child = parent * 2;
if ((child < H->size) && (H->data[child] < H->data[child + 1])) // child指向左右子节点中的较大者
child++;
if (H->data[child] > H->data[parent])
{ // 若子节点大于父节点,则交换两者位置
temp = H->data[parent];
H->data[parent] = H->data[child];
H->data[child] = temp;
}
else
break;//当前节点无需调整,进入下一个循环
for (parents = child; parents * 2 <= H->size; parents = childs)
{ // 遍历调整节点后的子树,看是否符合最大堆
childs = parents * 2;
if ((childs < H->size) && (H->data[childs] < H->data[childs + 1]))
childs++; // childs指向左右子节点中的较大者
if (H->data[childs] > H->data[parents])
{ // 若子节点大于父节点,则交换两者位置
temps = H->data[parents];
H->data[parents] = H->data[childs];
H->data[childs] = temps;
}
}
}
}
题目
堆中的路径
哈夫曼树
带权路径长度最小的树,也称最优二叉树,构造方法是每次把权值最小的两棵二叉树合并,具体过程在离散数学的笔记上有例题
特点
- 没有度为1的节点
- n个叶子节点的哈夫曼树共有2n-1个节点
- 哈夫曼树的任意非叶结点的左右子树交换后仍是哈夫曼树
- 对同一组权值,可能存在不同构的多棵哈夫曼树
实现
这里用最小堆实现哈夫曼树,具体代码参见数据结构(十)哈夫曼树_给几个叶子结点,构造多少种哈夫曼树-CSDN博客
题目
Huffman Codes
树之习题选讲- Huffman Codes①_哔哩哔哩_bilibili
《数据结构》05-树9 Huffman Codes_huffman codes-CSDN博客
查并集的实现和优化
定义
并查集:集合并、查某元素属于什么集合
可以使用树结构表示集合,每个节点代表一个集合元素,采用数组存储
运算
题目
File Transfer
小白专场:File Transfer①_哔哩哔哩_bilibili
《数据结构》05-树8 File Transfer_tree8文件-CSDN博客