通知
由于本篇过大,hexo架构渲染时有点问题,这里有pdf版本
3. 考试范围和重点
1-6章,以本文最后的内容为复习重点,尤其是★标出部分为重中之重。考试时如果涉及到本大纲没有列出的内容,那么试卷中会给出足够的定义和性质。
遗留问题
第1章 概论
一. 重要概念
- 抽象数据结构 2. 数据逻辑结构 3.数据存储结构 4. 算法 ★ 5. 算法分析(时间代价、空间代价) 6. 数据结构的选择和评价
二. 方法
- 根据二元组画出图示逻辑结构(注意边的方向)
★ 2. 根据要求设计数据结构
★ 3. 算法的渐进分析方法
★ 4.算法分析的大O表示法(不要求掌握大Ω、大Θ表示法)
数据结构
- 涉及数据之间的逻辑关系,数据在计算机中的存储表示和在这种结构上的一组能执行的操作(运算)三个方面
- 三要素
逻辑结构
具体问题的数学抽象,反映事物的组成和逻辑关系
逻辑结构的表示:
存储结构
抽象数据类型
抽象数据类型由<数据对象、数据关系、数据操作>
三个不可分割的部分组成的三元组
ADT抽象数据类型名{
数据对象D:<数据对象的定义>
数据关系S: <数据关系的定义>
数据操作P:<基本操作的定义>
}ADT抽象数据类型名
==算法分析==
- 算法分析是对一个算法需要多少计算时间和存储时间作定量的分析
- 判断所提出的算法==是否符合现实情况==
- 时间和空间复杂性
==算法的渐进分析==
$$
f(n)=n^2+100n+log_{10}n+1000
$$
- 算法分析:估计当数据规模n逐步增大时,(时间或者空间)资源开销f(n)的增长规模
- 从数量级大小的比较来考虑,当n增大到一定值时,资源开销的计算公式中影响最大的就是n的幂次最高的项,其他的常数项和低幂次项均可忽略不计
==大O表示法==
二分查找法时间复杂度推导
二分查找法(Binary Search)是一种高效的查找算法,适用于已排序的数组或列表。它通过反复将查找范围缩小一半来快速定位目标元素。其时间复杂度为 ( O(\log n) )。下面将详细推导二分查找法的时间复杂度。
一、二分查找法的基本原理
假设有一个升序排列的数组 ( A ) ,包含 ( n ) 个元素。目标是查找某个特定的元素 ( x ) 是否存在于数组中,并找到其位置。二分查找的步骤如下:
- 确定查找范围:初始时,查找范围为整个数组,即从下标 ($$ \text{low} = 0$$ ) 到 ($$ \text{high} = n-1$$ )。
- 找到中间元素:计算中间下标
$$
\text{mid} = \left\lfloor \frac{\text{low} + \text{high}}{2} \right\rfloor
$$
然后比较 ( $$A[\text{mid}]$$ ) 与 ( x ):
- 如果 ( $$A[\text{mid}] = x ),查找成功,返回 ( \text{mid} )$$。
- 如果 ( $$A[\text{mid}] < x $$),则目标元素 ( x ) 只能在右半部分,即更新查找范围为 ( $$\text{low} = \text{mid} + 1$$ ) 到 ($$ \text{high}$$ )。
- 如果 ( $$A[\text{mid}] > x$$ ),则目标元素 ( x ) 只能在左半部分,即更新查找范围为 ( $$\text{low} $$) 到 ( $$\text{mid} - 1$$ )。
- 重复步骤2,直到找到目标元素或查找范围为空。
二、时间复杂度的推导
1. 每一步缩小查找范围的大小
在每一步操作中,二分查找法将当前的查找范围缩小一半。这意味着:
- 初始时,查找范围大小为 ( n )。
- 第一次比较后,查找范围缩小到
$$
\frac{n}{2}
$$
$$
\frac{n}{4}
$$
$$
\frac{n}{2^k}
$$
2. 确定最坏情况下需要的比较次数
最坏情况下,二分查找需要继续缩小查找范围,直到查找范围大小为1,即:
$$
\frac{n}{2^k} \leq 1
$$
解这个不等式:
$$
n \leq 2^k \
\Rightarrow \log_2 n \leq k
$$
因此,最坏情况下需要的比较次数 ( k ) 至少为
$$
\log_2 n
$$
3. 递归关系的推导
可以通过递归关系进一步确认时间复杂度。
设 ( T(n) ) 为在大小为 ( n ) 的数组上进行二分查找的时间复杂度。根据二分查找的步骤,每次比较后将问题规模缩小一半,并进行常数时间的比较操作。因此递归关系为:
$$
T(n) = T\left(\frac{n}{2}\right) + c
$$
其中,( c ) 是常数时间(比较操作)的时间。
通过递归展开:
$$
T(n) = T\left(\frac{n}{2}\right) + c \
= T\left(\frac{n}{4}\right) + 2c \
= T\left(\frac{n}{8}\right) + 3c \
\vdots \
= T\left(\frac{n}{2^k}\right) + kc
$$
当
$$
\frac{n}{2^k} = 1
$$
即 ( k = \log_2 n ) 时,递归终止:
$$
T(n) = T(1) + c \log_2 n
$$
由于 ( T(1) ) 是常数,最终时间复杂度为:
$$
T(n) = O(\log n)
$$
4. 示例说明
假设有一个大小为 ( n = 16 ) 的数组,进行二分查找的步骤如下:
- 第一次比较:查找范围 ( 16 ) 个元素,比较中间的第 ( 8 ) 个元素。
- 第二次比较:查找范围缩小到 ( 8 ) 个元素,比较中间的第 ( 4 ) 个元素。
- 第三次比较:查找范围缩小到 ( 4 ) 个元素,比较中间的第 ( 2 ) 个元素。
- 第四次比较:查找范围缩小到 ( 2 ) 个元素,比较中间的第 ( 1 ) 个元素。
- 第五次比较:查找范围缩小到 ( 1 ) 个元素,比较最终元素。
可以看到,比较次数为
$$
\log_2 16 = 4
$$
对于一般情况,大小为 ( n ) 的数组,最多需要
$$
\log_2 n
$$
次比较。
三、结论
二分查找法通过每次将查找范围缩小一半,使得其时间复杂度为对数级别,即
$$
O(\log n)
$$
这种高效的查找性能使得二分查找在处理大规模已排序数据时非常有用。
第2章 线性表
一. 概念
- 线性表 2. 单链表 3. 双链表 4. 循环表
二. 方法
- 顺序表上实现的运算
★ 2.链表上实现的运算(指针操作的正确性)
- 顺序表和链表的比较
链表
==插入和删除要注意边界==
链表检索
链表插入
ListNode* Insert(int i,T value){ ListNode *p,*q; q=new ListNode; p=setPos(i-1); if(p==NULL)return false; q->data=value; q->next=p->next; p->next=q; if(q->next==NULL) tail=q; return true; }
|
链表删除
template<class T> bool lnkList<T>::delete(const int i){ Link<T>*p,*d; if((p=setPos(i-1))==NULL||p==tail){ cout<<"非法删除点"<<endl; return false; } d=p->next; if(d=tail){ tail=p; p->next=NULL; delete d; } else{ p->next=d->next; delete d; } return true; }
|
线性表实现方法的比较
- 顺序表的主要优点
- 没有使用指针,不用花费额外开销
- 线性表元素的访问非常便利
- 链表的主要优点
- 无需事先了解线性表的长度‘
- 允许线性表的长度动态变化
- 能够适应经常插入删除内部元素的情况
- 顺序表适合存储静态数据,链表适合动态数据
第3章 栈与队列
一. 概念
- 栈 2. 队列 3. 循环队列
二. 方法
★ 1. 栈的性质,用栈来生成序列,栈的实现
- 队列的性质,用队列生成序列
★3. 循环队列的实现
★ 4. 利用栈来消除递归
- 栈的灵活应用,例如表达式求值 (中缀表达式转后缀表达式的算法、后缀表达式求值算法)
==栈的性质==
==栈的实现==
栈的ADT
template <class T> class Stack {
public:
void clear();
bool push(const T item);
bool pop(T &item);
bool top(T &item);
bool isEmpty();
bool isFull(); };
|
顺序栈
溢出
- 上溢(overflow)
- 当栈中已经有maxsize个元素时,如果再做进栈操作,所产生的”无空间可用”现象
- 下溢(underflow)
链式栈
- 栈的链式存储结构,是运算受限的链表
- 指针方向:==从栈顶向栈底==
- 只能在链表头部进行操作,==故链表没有必要像单链表那样附加头结点。==
- 栈顶指针就是链表的头指针(top)
- 无栈满问题(但存在栈空约束)
链式栈的创建
压栈
bool InkStack<T>::push(const T item){ Link<T>* tmp=new Link<T>(item,top) top = tmp; size++; return true; }
|
弹栈
bool InkStack<T>::pop(T& item){ Link<T>*tmp; if(size==0){ cout<<"栈为空,不能执行出栈操作"<<endl; return false; } item =top->data; tmp= top->next; delete top; top =tmp; size--; return true; }
|
两者比较
==利用栈来消除递归==
简单的递归转换
阶乘递归实现
long factorial(long n){ if(n<=0) return 1; return n*factorial(n-1); }
|
阶乘非递归实现
long factorial(long n){ long m=1; for(long i=1;i<=n;i++){ m=m*i; } return m; }
|
==尾递归==
- 指函数的最后一个动作时调用函数本身的递归函数,是递归的一种特殊情形
- 尾递归的本质是:将单词计算的结果缓存起来,传递给下次调用,相当于自动累积
int factorial_tail(int n, int acc = 1) { if (n == 1) { return acc; } return factorial_tail(n - 1, n * acc); }
|
==机械的递归转换==
方法步骤:
我们有n件物品,物品i的重量为w[i]
。如果限定每种物品:要么完全放进背包,要么不放进背包,即物品是不可分割的。
问:能否从这n件物品中选择若干件放入背包,使其重量之和恰好为s
我们来看递归版本
递归关系
$$
knap(s,n)=\begin{cases}true,当s=0\
false,当s<0或s>0且n<1\
knap(s-w[n-1],n-1)||knap(s,n-1),当s>0且n>=1 (两种情况,是否取最后一项)
\end{cases}
$$
bool knap(int s,int n){ if(s==0){ return true; } else if(s<0||(s>0&&n<1)){ return false; } if(knap(s-w[n-1],n-1)){ cout<<w[n-1]; return true; } else{ return knap(s,n-1); } }
|
- 1.设置一工作栈保存当前工作记录
- 在函数中出现的所有参数和局部变量都必须用栈中的数据成员代替
enum rdType{0,1,2}; public class knapNode{ int s,n; rdType rd; bool k; };
|
2.设置 t+2个语句标号
- label 0:第一个可执行语句
- label t+1:设置在函数题结束处
- label i(1<=i<=t):第i个递归返回处
3.增加非递归入口
Stack<knapNode>stack; knapNode tmp; tmp.s=s;tmp.n=n;tmp.rd=0; stack.push_back(tmp)
|
- 5.所有递归出口处增加语句
- 6.标号为t+1的语句
stack.pop(&tmp); switch(tmp.rd){ case 0:return ; case 1:goto label1;break; case t:goto labelt ;break; default: break; }
|
enum rdType{0,1,2}; public class knapNode{ int s,n; rdType rd; bool k; }; bool nonRecKnap(int s,int n){ Stack<knapNode>stack; knapNode tmp; tmp.s=s;tmp.n=n;tmp.rd=0; stack.push_back(tmp) label 0: stack.pop(&tmp); if(tmp.s==0){ tmp.k=true; goto label3; } if((tmp.s<0)||(tmp.s>0&&tmp.n<1)){ tmp.k=false; goto label3; } stack.push(tmp); x.s=tmp.s-w[tmp.n-1]; x.n=tmp.n-1; x.rd=1; stack.push(x); goto label0; label1: stack.pop(&x); if(tmp.k==true){ x.k=true; stack.push(x); cout<<w[x.n-1]<<endl; goto label3; } stack.push(x); tmp.s=x.s;tmp.n=x.n-1;tmp.rd=2; stack.push(tmp); goto label0; label2: stack.pop(&x); x.k=tmp.k; stack.push(x); label3: stack.pop(&tmp); switch(tmp.rd){ case 0:return tmp.k; case 1:goto label1; case 2:goto label2; } }
|
==合法出栈序列==
- 给定一个入栈序列s(string 类型),要求输出一个result(vector类型),result中存储着s的所有合法出栈序列
#include <iostream> #include <vector> #include <string> #include <stack>
using namespace std;
class Solution { public: vector<string> generatePopSequences(const string& s) { vector<string> result; string current; stack<char> stk; backtrack(s, 0, stk, current, result); return result; }
private: void backtrack(const string& s, int index, stack<char>& stk, string& current, vector<string>& result) { if (index == s.size() && stk.empty()) { result.push_back(current); return; }
if (index < s.size()) { stk.push(s[index]); backtrack(s, index + 1, stk, current, result); stk.pop(); }
if (!stk.empty()) { char top = stk.top(); stk.pop(); current += top; backtrack(s, index, stk, current, result); current.pop(); stk.push(top); } } };
int main() { Solution solution; string s = "ABC"; vector<string> sequences = solution.generatePopSequences(s);
cout << "所有合法的出栈序列为:" << endl; for (const auto& seq : sequences) { cout << seq << endl; }
return 0; }
|
卡特兰数:$$\frac{1}{n+1} \binom{2n}{n} $$
其表示从(0,0)到(n,n)不穿过对角线的路径数
那么我们可以将栈的出栈序列这样抽象化,我们知道,在栈未完全弹出前,出栈的次数一定小于等于入栈的次数,我们将入栈看做(x,y)到(x+1,y),出栈看做(x,y)到(x,y+1),每一个路径都对应于一个出栈序列,那么我们可以知道,该路径肯定不会穿过对角线,因为栈的次数一定小于等于入栈的次数。则出栈序列个数就是卡特兰数
队列
队列的ADT
==循环队列==
实现
入队:
arr[rear]=item;
rear=(rear+1)%mSize;
出队:
item=arr[front];
front=(front+1)%mSize;
判断队满:
(rear+1)%mSize==front;
判断队空
front==rear
链式队列
作业题
证明:
必要性(若输出序列可由栈操作得到,则不存在 (i < j < k),使得 (p_i > p_k > p_j):
假设输出序列 (p_1, p_2, \dots, p_n) 可以通过栈操作得到。我们要证明在该序列中不存在下标 (i < j < k),使得 (p_i > p_k > p_j)。
假设反设,存在 (i < j < k),使得 (p_i > p_k > p_j)。考虑栈操作过程中的这三个元素:
元素 (p_i) 的出栈: 在位置 (i),元素 (p_i) 被弹出栈。这意味着在此之前,元素 (1) 到 (p_i) 已经被压入栈或弹出。
元素 (p_j) 的出栈: 在位置 (j),元素 (p_j) 被弹出栈。由于 (p_j) 在 (p_i) 之后被弹出,且 (i < j),因此 (p_j) 必须在 (p_i) 被弹出后仍留在栈中。这意味着 (p_j) 在 (p_i) 之前被压入栈,但在 (p_i) 被弹出后才被弹出。
元素 (p_k) 的出栈: 在位置 (k),元素 (p_k) 被弹出栈。由于 (p_k) 介于 (p_i) 和 (p_j) 之间,且 (p_i > p_k > p_j),这意味着在 (p_i) 被弹出后,(p_k) 被压入栈,并在 (p_j) 被弹出前被弹出。
然而,这与栈的后进先出性质矛盾。因为 (p_j) 在 (p_i) 之前被压入栈,但在 (p_k) 之后被弹出,这不符合栈的操作规则。因此,假设不成立,输出序列中不存在这样的 (i, j, k)。
充分性(若输出序列中不存在 (i < j < k),使得 (p_i > p_k > p_j),则可以通过栈操作得到该序列):
我们将构造一个栈操作序列,使得输入序列 (1, 2, \dots, n) 可以通过栈操作得到输出序列 (p_1, p_2, \dots, p_n)。
算法步骤:
初始化: 设栈为空,输入指针指向 (1),输出指针指向 (p_1)。
循环操作:
- 步骤 A: 若栈顶元素等于当前输出指针指向的元素 (p_j),则弹出栈顶元素,并将输出指针移动到下一个元素。
- 步骤 B: 否则,若输入指针未超过 (n),则将输入指针指向的元素压入栈,并将输入指针移动到下一个元素。
- 步骤 C: 若输入指针已超过 (n),但栈顶元素不等于 (p_j),则算法失败,无法生成输出序列。
结束条件: 当输出指针超过 (n) 时,算法成功。
证明算法的正确性:
因此,该算法能够成功地将输入序列通过栈操作得到输出序列 (p_1, p_2, \dots, p_n)。
结论: 从初始输入序列 (1, 2, \dots, n),可以利用一个栈得到输出序列 (p_1, p_2, \dots, p_n) 的充分必要条件是:输出序列中不存在下标 (i < j < k),使得 (p_i > p_k > p_j)。
第4章 字符串
一. 概念
- 串 2. 模式匹配
二. 方法
- 串的基本操作
- 串的存储及运算
★ 3. 串的KMP快速模式匹配算法,求特征向量数组(N数组)和利用N向量完成匹配的方法(注意变种KMP算法的特征定义、特征向量和KMP算法在字符串相关问题中的灵活应用)
串的KMP快速模式匹配算法
非优化的算法:(就是简单的最大的公共前后缀的长度)
int* findNext(string p){ int i,k; int m=P.length(); int* next=new int[m]; next[0]=-1; i=0;k=-1; while(i<m-1){ while(k>=0&&P[k]!=P[i]){ k=next[k]; } i++;k++; next[i]=k; } return next; }
|
- 当P[i]==P[k]时,考虑我们在比较P和T时,既然P[i]已经和T[j]不匹配了,我们回溯到P[k],还是不匹配地,不如直接回溯到next[k]
因此,回溯算法是
int* findNext(string P){ int i,k; int m=P.length(); int *next=new int[m]; next[0]=-1; i=0;k=-1; while(i<m-1){ while(k>=0&&P[k]!=P[i]){ k=next[k]; } i++;k++; if(P[k]==P[i]){ next[i]=next[k]; } else{ next[i]=k; } } return next; }
|
==KMP模式匹配算法的==
int KMPStrMatching(string T,string P,int *N,int start){ N=findNext(P); int i=0; int j=start; int pLen=P.length(); int tLen=T.length(); if(tLen-start<pLen){ return -1; } while(i<pLen&&j<tLen){ if(i==-1||T[j]==P[i]){ i++,j++; } else{ i=N[i]; } } if(i>=pLen){ return (j-pLen+1); } else return -1; }
|
算法复杂度分析
先看求next的数组的复杂度
我们注意到在while循环中i只增不减,因此时间复杂度为O(m)
对于KMP匹配,我们又注意到在while循环中j只增不减
因此KMP算法的时间复杂度是O(n+m)
第5章 二叉树
一. 概念
- 二叉树 2.二叉树的深度优先遍历 3. 二叉搜索树BST 4. 堆 5. Huffman树、Huffman编码
二. 方法
1.二叉树的链式存储(1)二叉链表(2)带父指针的三重链表
- 二叉树的顺序存储、完全二叉树的顺序存储
★ 3. 二叉树的深度优先遍历。要求自己能用递归解决二叉树应用问题;看得懂非递归二叉树遍历框架、可以完成采用非递归算法设计的算法填空
- 二叉树的广度优先遍历及其应用
★ 5. 二叉搜索树的插入与删除
★ 6. 构造Huffman树,利用Huffman树进行编码、解码
★ 7. 堆的建立与维护过程
思考题
==部分题在手写笔记上==
- N个节点的二叉树有多少种不同的形态?
- 是一个Catalan数,即,$$\frac{1}{n+1}\binom{2n}{n}$$
考虑其递归即可
定义
满二叉树
如果一棵二叉树的结点,或为树叶(0度节点),或为两颗非空子树(2度节点),则称作满二叉树
完全二叉树
==完全二叉树的特点==
- 叶节点只可能在最下面两层出现
- 路径长度和最短(满二叉树不具有此性质)
扩充二叉树
重要性质
==二叉树的周游==
template<class T> void BinaryTree<T>::DepthOrder(BinaryTreeNode<T>* root){ if(root!=NULL){ Visit(root); DepthOrder(root->leftchild()); Visit(root); DepthOrder(root->rightchild()); Visit(root); } }
|
#include<iostream> #include<vector> using namespace std;
vector<int> result;
void dfs(vector<int>middle,vector<int>post){ if(middle.size()==0){ return; } if(middle.size()==1){ result.push_back(middle[0]); return; } result.push_back(post[post.size() - 1]);
int pos = 0; for (int i = 0; i < middle.size();i++){ if(middle[i]==post[post.size() - 1]){ pos = i; } } vector<int> middle_pre(middle.begin(), middle.begin() + pos); vector<int> middle_next(middle.begin() + pos + 1, middle.end()); vector<int>post_pre(post.begin(), post.begin() + pos); vector<int>post_next(post.begin() + pos, post.end() - 1); dfs(middle_pre, post_pre); dfs(middle_next, post_next); return; }
int main(){ vector<int> num; int i; while(cin>>i){ num.push_back(i); } vector<int> middle; vector<int> post; for(int i=0;i<num.size()/2;i++){ middle.push_back(num[i]); } for(int i=num.size()/2;i<num.size();i++){ post.push_back(num[i]); } dfs(middle, post); for(int i=0;i<result.size();i++){ cout<<result[i]<<" "; } }
|
==非递归深度优先遍历==
前序遍历非递归算法
#include <iostream> #include <stack> #include <vector> using namespace std;
struct TreeNode { int data; TreeNode *left; TreeNode *right; TreeNode(int t, TreeNode *leftt = nullptr, TreeNode *rightt = nullptr) : data(t), left(leftt), right(rightt) {} };
vector<int> dfs_pre(TreeNode *root) { vector<int> result; if (root == nullptr) { return result; }
stack<TreeNode *> st; st.push(root);
while (!st.empty()) { TreeNode *current = st.top(); st.pop(); result.push_back(current->data);
if (current->right) { st.push(current->right); } if (current->left) { st.push(current->left); } }
return result; }
TreeNode *createSampleTree() {
TreeNode *root = new TreeNode(1); root->left = new TreeNode(2, new TreeNode(4), new TreeNode(5)); root->right = new TreeNode(3); return root; }
void deleteTree(TreeNode *root) { if (root == nullptr) return; deleteTree(root->left); deleteTree(root->right); delete root; }
int main() { TreeNode *root = createSampleTree();
vector<int> traversal = dfs_pre(root);
cout << "前序DFS遍历结果: "; for (int val : traversal) { cout << val << " "; } cout << endl;
deleteTree(root);
return 0; }
|
非递归中序遍历二叉树算法
#include <iostream> #include <stack> #include <vector> using namespace std;
struct TreeNode { int data; TreeNode *left; TreeNode *right; TreeNode(int t, TreeNode *leftt = nullptr, TreeNode *rightt = nullptr) : data(t), left(leftt), right(rightt) {} };
vector<int> dfs_inorder(TreeNode *root) { vector<int> result; stack<TreeNode *> st; TreeNode *current = root;
while (current != nullptr || !st.empty()) { while (current != nullptr) { st.push(current); current = current->left; }
current = st.top(); st.pop(); result.push_back(current->data);
current = current->right; }
return result; }
TreeNode *createSampleTree() {
TreeNode *root = new TreeNode(1); root->left = new TreeNode(2, new TreeNode(4), new TreeNode(5)); root->right = new TreeNode(3); return root; }
void deleteTree(TreeNode *root) { if (root == nullptr) return; deleteTree(root->left); deleteTree(root->right); delete root; }
int main() { TreeNode *root = createSampleTree();
vector<int> traversal = dfs_inorder(root);
cout << "中序DFS遍历结果: "; for (int val : traversal) { cout << val << " "; } cout << endl;
deleteTree(root);
return 0; }
|
非递归后序遍历二叉树
#include <iostream> #include <stack> #include <vector> using namespace std;
struct TreeNode { int data; TreeNode *left; TreeNode *right; TreeNode(int t, TreeNode *leftt = nullptr, TreeNode *rightt = nullptr) : data(t), left(leftt), right(rightt) {} }; enum Tags { Left, Right }; class StackElement { public: TreeNode *pointer; Tags tag; StackElement(TreeNode* cur,Tags tagg):pointer(cur),tag(tagg){} };
vector<int> dfs_inorder(TreeNode *root) { vector<int> result; stack<StackElement> st; TreeNode *current = root; while(current!=NULL||!st.empty()){ while(current){ st.push(StackElement(current, Left)); current = current->left; } if(!current){ StackElement element = st.top(); current = element.pointer; st.pop(); if(element.tag==Left){ element.tag = Right; st.push(element); current = current->right; } else{ result.push_back(current->data); current = NULL; } } }
return result; }
TreeNode *createSampleTree() {
TreeNode *root = new TreeNode(1); root->left = new TreeNode(2, new TreeNode(4), new TreeNode(5)); root->right = new TreeNode(3); return root; }
void deleteTree(TreeNode *root) { if (root == nullptr) return; deleteTree(root->left); deleteTree(root->right); delete root; }
int main() { TreeNode *root = createSampleTree();
vector<int> traversal = dfs_inorder(root);
cout << "后序DFS遍历结果: "; for (int val : traversal) { cout << val << " "; } cout << endl;
deleteTree(root);
return 0; }
|
==广度优先遍历二叉树==
==二叉搜索树的插入和删除==
二叉搜索树(BST
),二叉排序树
- 或者是一颗空树
- 或者是具有下列性质的二叉树
- 对于任何一个节点,设其值为K,则该节点的左子树(若不空)的任意一个值都小于K
- 该节点的右子树(若不空)的任意一个节点的值都大于K
- 而且它的左右子树也分别为二叉搜索树
二叉搜索树的插入
思想
#include <iostream> #include <stack> #include <vector> using namespace std;
struct TreeNode { int data; TreeNode *left; TreeNode *right; TreeNode(int t, TreeNode *leftt = nullptr, TreeNode *rightt = nullptr) : data(t), left(leftt), right(rightt) {} }; void InsertNode(TreeNode* root,TreeNode* newpointer){ TreeNode *pointer; if(!root){ root = newpointer; return; } else{ pointer = root; } while(1){ if(newpointer->data==pointer->data) return; else if(newpointer->data<pointer->data){ if(pointer->left==NULL){ pointer->left = newpointer; return; } else{ pointer = pointer->left; } } else{ if(pointer->right==NULL){ pointer->right = newpointer; return;
} else{ pointer = pointer->right; } } } }
|
删除
未优化
改进
template <class T> void BinarySearchTree<T>::DeleteNodeEx(BinaryTreeNode<T> *delpointer) { BinaryTreeNode<T> *replpointer; BinaryTreeNode<T> *replparent = NULL; BinaryTreeNode<T> *delparent = Parent(delpointer);
if (delpointer->leftchild() == NULL) { replpointer = delpointer->rightchild(); } else { replpointer = delpointer->leftchild(); while (replpointer->rightchild() != NULL) { replparent = replpointer; replpointer = replpointer->rightchild(); } if (replparent == NULL) { delpointer->left = replpointer->leftchild(); } else replparent->right = replpointer->leftchild(); replpointer->left = delpointer->leftchild(); } replpointer->right = delpointer->rightchild();
if (delparent == NULL) root = replpointer; else if (delparent->leftchild() == delpointer) delparent->left = replpointer; else delparent->right = replpointer;
delete delpointer; delpointer = NULL; return; }
|
==堆的建立和维护==
是完全二叉树
#include <iostream> #include <vector> using namespace std;
template <class T> class MinHeap { private: T *heapArray; int CurrentSize; int MaxSize; void BuildHeap(); void swap(int i, int j) { T temp = heapArray[i]; heapArray[i] = heapArray[j]; heapArray[j] = temp; }
public: MinHeap(const int n, T heaparray[]) { if (n <= 0) return; CurrentSize = n; MaxSize = 10000; heapArray = new T[MaxSize]; for (int i = 0; i < n; i++) { heapArray[i] = heaparray[i]; } BuildHeap(); }
virtual ~MinHeap() { delete[] heapArray; }
int leftchild(int pos) const { return 2 * pos + 1; }
int rightchild(int pos) const { return 2 * pos + 2; }
int parent(int pos) const { return (pos - 1) / 2; }
void SiftDown(int pos) { int i = pos; int j = 2 * i + 1; T temp = heapArray[i]; while (j < CurrentSize) { if ((j + 1 < CurrentSize) && heapArray[j] > heapArray[j + 1]) { j++; } if (temp > heapArray[j]) { heapArray[i] = heapArray[j]; i = j; j = 2 * i + 1; } else { break; } } heapArray[i] = temp; }
void SiftUp(int pos) { int temppos = pos; T temp = heapArray[temppos]; while ((temppos > 0) && (heapArray[parent(temppos)] > temp)) { heapArray[temppos] = heapArray[parent(temppos)]; temppos = parent(temppos); } heapArray[temppos] = temp; }
bool Insert(const T &newNode) { if (CurrentSize == MaxSize) { return false; } heapArray[CurrentSize] = newNode; SiftUp(CurrentSize); CurrentSize++; return true; }
T RemoveMin() { if (CurrentSize == 0) { cout << "Can't Delete"; exit(1); } else { T minValue = heapArray[0]; heapArray[0] = heapArray[--CurrentSize]; if (CurrentSize > 1) SiftDown(0); return minValue; } }
bool Remove(int pos, T &node) { if ((pos < 0) || pos >= CurrentSize) { return false; } node = heapArray[pos]; heapArray[pos] = heapArray[--CurrentSize]; SiftUp(pos); SiftDown(pos); return true; } };
template <class T> void MinHeap<T>::BuildHeap() { for (int i = parent(CurrentSize - 1); i >= 0; i--) { SiftDown(i); } }
|
==构造Huffman树,利用Huffman树进行编码、解码==
定义:具有最小带权路径长度的二叉树称作哈夫曼 (Huffman)树(或称最优二叉树)
建立Huffman编码树的过程
编码过程
译码
第6章 树
一. 概念
- 树、森林 2. 树的先根遍历、后根遍历、层次遍历 ★3. K叉树
二. 方法
★ 1. 森林与二叉树相互转换
2.森林的链式存储
★ (1) 转换为相应的二叉树,用二叉链表示
(2) 父指针表示法
(3) 子结点表表示法
(4)等价类和并查算法的应用
★ 3. 森林的深度优先遍历(递归),可能结合应用
- 森林的层次遍历(用队列),可能结合应用
★ 5. 森林/二叉树的顺序存储(不必死记各种顺序存储方法,要了解原理。其本质是按照遍历的性质,把内存中森林/二叉树输出一个顺序存储的序列,反过来也可以根据相应的顺序存储的序列构造内存中的森林/二叉树)
==森林和二叉树的相互转换==
森林转换成二叉树
BinaryTreeNode *forestToBinaryTree(const Forest &forest) { if (forest.empty()) return nullptr;
BinaryTreeNode *root = nullptr; BinaryTreeNode *current = nullptr;
for (TreeNode *tree : forest) { if (!root) { root = treeToBinaryTree(tree); current = root; } else { current->right = treeToBinaryTree(tree); current = current->right; } } return root; }
|
树换成二叉树
BinaryTreeNode *treeToBinaryTree(TreeNode *root) { if(!root) { return NULL; } BinaryTreeNode *binarytreeroot = new BinaryTreeNode(root->value); if(root->children.size()) { binarytreeroot->left = treeToBinaryTree(root->children[0]); BinaryTreeNode *current = binarytreeroot->left; for (int i = 1; i < root->children.size(); i++) { current->right = treeToBinaryTree(root->children[i]); current = current->right; } } return binarytreeroot; }
|
二叉树转换成森林
Forest binaryTreeToForest(BinaryTreeNode *root) { Forest forest; if (!root) return forest; BinaryTreeNode *current = root; while(current->right) { forest.push_back(binaryTreeToTree(current->right)); current = current->right; } forest.push_back(binaryTreeToTree(root)); return forest; }
|
二叉树为树
TreeNode *binaryTreeToTree(BinaryTreeNode *root) { if (!root) return nullptr;
TreeNode *treeRoot = new TreeNode(root->value);
if (root->left) { treeRoot->children.push_back(binaryTreeToTree(root->left)); BinaryTreeNode *current = root->left; while (current->right) { treeRoot->children.push_back(binaryTreeToTree(current->right)); current = current->right; } } return treeRoot; }
|
树的周游
==树的链式存储==
==这种方法本质是将树转化成二叉树,再存储该二叉树的二叉链的表示==
树的抽象数据类型的实现
#include<iostream> #include<vector> #include<queue> using namespace std;
template<class T> class TreeNode{ public: T value; TreeNode<T> *pChild; TreeNode<T> *pSibling; public: TreeNode(T a):this->value(a){ pChild = NULL; pSibling = NULL; } bool TreeNode<T> isLeaf(){ return pChild == NULL; } void InsertFirst(TreeNode<T>* node){ if(pChild){ node->pSibling = pChild; pChild = node; } else{ pChild = node; } } };
template<class T> class Tree{ TreeNode<T> *root; public: TreeNode<T> *Parent(TreeNode<T> *current){ queue<TreeNode<T> *> que; TreeNode<T> *pointer = root; TreeNode<T> *last_pointer = NULL; if(!current || current == pointer){ return NULL; } else{ while(pointer){ if(current == pointer){ return NULL; } que.push(pointer); pointer = pointer->pSibling; } } while(!que.empty()){ last_pointer = que.front(); que.pop(); pointer = last_pointer->pChild; while(pointer){ if(pointer == current){ return last_pointer; } que.push(pointer); pointer = pointer->pSibling; } } } TreeNode<T> *PrevSibling(TreeNode<T> *node) { TreeNode<T> *pointer = Parent(node); TreeNode<T> *ppChild; if (pointer) { ppChild = pointer->pChild; } else { ppChild = root; }
if (ppChild == node) { return NULL; }
while (ppChild) { if (ppChild->pSibling == node) { return ppChild; } ppChild = ppChild->pSibling; }
return NULL; }
void DestoryNodes(TreeNode<T> *root){ if(!root){ return; } DestoryNodes(root->pChild); DestoryNodes(root->pSibling); delete root; } void DeleteSubTree(TreeNode<T> *subroot){ TreeNode<T> *pointer = PrevSibling(subroot); if(!pointer){ pointer = Parent(subroot); if(pointer){ pointer->pChild = subroot->pSibling; subroot->pSibling = NULL; } else{ root = subroot->pSibling; subroot->pSibling = NULL; } } else{ pointer->pSibling = subroot->pSibling; subroot->pSibling = NULL; } DestoryNodes(subroot); } };
|
==父指针表示法及在并查集中的应用==
并查集
- 并查集是一种特殊集合,由不相交子集构成
- 基本操作
- Find:判断两个节点是否在同一个集合中
- Union:归并两个集合
- 并查集可用于求解等价类问题
#include<iostream> #include<vector> using namespace std;
class partreenode{ public: int value; partreenode *parent=NULL; int nCount=0; void setParent(partreenode* p){ this->parent = p; } void setCount(int n){ this->nCount = n; } partreenode(int v):value(v){} partreenode(){} };
class partree{ public: vector<partreenode *> array; int size; int result; partree(int sizee,int resultt) :size(sizee),result(resultt){ for (int i = 0; i < size;i++){ array.push_back(new partreenode(i + 1)); } }; partreenode* find(partreenode* node)const{ if(node->parent==NULL){ return node; } node->setParent(find(node->parent)); return node->parent; } void Union(int i, int j){ partreenode *pointeri = find(array[i]); partreenode *pointerj = find(array[j]); if(pointeri!=pointerj){ if(pointeri->nCount>=pointerj->nCount){ pointerj->setParent(pointeri); pointeri->setCount(pointeri->nCount + pointerj->nCount); } else{ pointeri->setParent(pointerj); pointerj->setCount(pointeri->nCount + pointerj->nCount); } this->result--; } } bool Different(int i, int j){ partreenode *pointeri = find(array[i]); partreenode *pointerj = find(array[j]); return pointeri != pointerj; } };
int main(){ int m, n; int i = 1; while(cin >> n >> m){ if(n==0&&m==0){ break; } partree *tree = new partree(n, n); for (int i = 0; i < m; i++) { int a, b; cin >> a >> b; tree->Union(a - 1, b - 1); } cout << "Case "<<i<<": " << tree->result << endl; i++; } }
|
==树的顺序存储方法==
本质是按照树遍历的次序进行节点存储
带右链的先根次序表示法
先转换成二叉树,再搞
带双标记位的先根次序表示法
template <class T> class DualTagTreeNode { public: T info; int ltag; int rtag; DualTagTreeNode(); virtual ~DualTagTreeNode(); };
template <class T> DualTagTreeNode<T>::DualTagTreeNode() { ltag = 1; rtag = 1; }
template <class T> DualTagTreeNode<T>::~DualTagTreeNode() {}
template <class T> Tree<T>::Tree(DualTagTreeNode<T> *nodeArray, int count) { using std::stack; stack<TreeNode<T> *> aStack; TreeNode<T> *pointer = new TreeNode<T>; root = pointer;
for (int i = 0; i < count - 1; i++) { pointer->setValue(nodeArray[i].info); if (nodeArray[i].rtag == 0) aStack.push(pointer); else pointer->setSibling(NULL);
TreeNode<T> *tempPointer = new TreeNode<T>; if (nodeArray[i].ltag == 0) pointer->setChild(tempPointer); else { pointer->setChild(NULL); pointer = aStack.pop(); pointer->setSibling(tempPointer); } pointer = tempPointer; }
pointer->setValue(nodeArray[count - 1].info); pointer->setChild(NULL); pointer->setSibling(NULL); }
|
带度数的后根次序表示法
带双标记的层次次序表示
#include <iostream> #include <queue> #include <vector>
template <class T> class Tree { public: TreeNode<T> *root;
Tree() : root(nullptr) {} ~Tree() { clear(root); }
Tree(const std::vector<DualTagTreeNode<T>> &nodeArray) { if (nodeArray.empty()) { root = nullptr; return; }
std::queue<TreeNode<T> *> nodeQueue; auto it = nodeArray.begin();
root = new TreeNode<T>(it->info); if (it->ltag == 0) { nodeQueue.push(root); } ++it;
while (it != nodeArray.end()) { if (nodeQueue.empty()) { std::cerr << "Invalid level order sequence!" << std::endl; break; }
TreeNode<T> *current = nodeQueue.front();
TreeNode<T> *child = new TreeNode<T>(it->info); current->child = child; if (it->ltag == 0) { nodeQueue.push(child); } if (it->rtag == 0) { TreeNode<T> *prevSibling = child; ++it; while (it != nodeArray.end() && prevSibling != nullptr) { if (prevSibling->sibling != nullptr) { break; }
TreeNode<T> *sibling = new TreeNode<T>(it->info); prevSibling->sibling = sibling; if (it->ltag == 0) { nodeQueue.push(sibling); }
if (it->rtag == 1) { break; }
prevSibling = sibling; ++it; } } else { ++it; }
if (it == nodeArray.end()) { break; }
if (current->child == nullptr || (it->ltag == 1 && it->rtag == 1)) { nodeQueue.pop(); } } }
private: void clear(TreeNode<T> *node) { if (node) { clear(node->child); clear(node->sibling); delete node; } } };
|