2018/01/05 自学数据结构知识总结
内存本是不存在任何结构,所有的内存都只是存储数据的单元,而栈、队列等都是人为的抽象出来的为了解决各种问题而构造的具有基于某种存储特性的数据单元。
栈:具有“先进后出”特性,规定这种先进后出特性的存储结构叫做“栈”,对于某一存储单元,假设它是一个箱子,六个面只有一个面是开放的,其他面都是封闭的,那么我们在对这个箱子进行操作时只能利用这唯一的开放的一个面,从这个面来放东西,或者取东西。这种存取东西的方式决定了这个箱子的存储东西时,只能先放进去的后取出来,因为被后放进去的东西挡住了,取不出来,因此,我们把具有这种特性的存储结构都叫做“栈”。这是方式人为规定的,计算机自身并没有规定你怎么存取数据。
队列:具有“先进先出”特性,同样,像栈一样,规定了一种存储方式,他是具有两个开放的面的箱子,这两个面是相对面,设为A,B面,规定只能从A面存数据,从B面取数据。那么在这种规定下,这个箱子便具有了“先进箱子的数据先出箱子”特性,把他叫做“队列”。同样,这是人为规定的为了解决问题而产生的存储结构,理解了这个,便对“栈”和“队列”不会产生陌生感,他就是普普通通的存储单元,只不过人为把他叫做“栈”、“队列”。
树:同样的,人为抽象出来的逻辑结构,他可以通过各种方式实现,数组,链表。我们在实现树时往往是根据具体的应用来考虑,到底是利用数组,还是链表。同样,我们在对树中的元素进行操作时,也会根据具体情况来判断到底是用“栈”这种方式操作数据,还是利用“队列”这种方式来操作数据。
插曲:因此目前我的理解是,数据结构这本书就是向我们介绍,前人们总结出来的,解决各类问题时所使用的各种对数据进行操作的结构,并通过算法具体实现的过程。
树的定义:由n个有限节点组成的一个具有层次关系的集合。
相关术语:
节点的度:一个节点含有的子树的个数称为该节点的度;
叶子节点或终端节点:度为0的节点称为叶子节点;
分支节点或非终端节点:度不为0的节点;
双亲节点或父节点,孩子节点或子节点,兄弟节点:略
树的度:一棵树中,最大的节点的度称为树的度;
节点的层次:从根节点的开始定义,根为第一次,根的子节点为第二层,以此类推;
树的高度或深度:最大层次;
二叉树:每个节点最多含有两个子树的树称为二叉树;
满二叉树:所有的分支节点都存在左子树和右子树,并且所有的叶子节点都在同一层上,这样就是满二叉树,就是完美圆满的意思,关键在于树的平衡。
根据满二叉树的定义,其具有以下特点:
1.叶子只能出现在下一层;
2.非叶子结点度一定是2;
3.在同样深度的二叉树中,满二叉树的节点个数最多,叶子树最多。
完全二叉树:对一颗具有n个节点的二叉树按层序排号,如果编号为i的节点与同样深度的满二叉树编号为i的节点在二叉树中的位置完全相同,就是完全二叉树,满二叉树一定是完全二叉树,反之不一定成立。具体可参考:https://www.cnblogs.com/polly333/p/4740355.html
根据完全二叉树定义,其具有以下特点:
1.叶子节点只能出现在最下一层;
2.最下层叶子节点只能连续集中在左侧位置;
3.倒数第二层若有叶子节点,则一定在右侧,且为连续位置;
4.同样节点数的二叉树,完全二叉树的深度最小(满二叉树)。
二叉树的遍历:
1.前序遍历:在结点第一次出现时,就输出它的值,即“先父节点(根节点),再左儿子(左子树),再右儿子(右子树)”,具体的实现可通过递归实现,例如:
void search(Tree root){
if(!root) return ; //若为空,返回
cout<<root->data<<endl; //输出值
search(root->left); //遍历左子树
search(root->right); //遍历右子树
}
2.中序遍历:在结点第二次出现时,输出它的值,即“先左子树,再根节点,再右子树”,实现方式和前序遍历只有很小的区别,如下:
void search(Tree root){
if(!root) return ;
search(root->left);
cout<<root->data<<endl; //在输出左子树之后再输出根节点
search(root->right);
}
3.后序遍历:在结点第三次出现时,输出它的值,即“先左后由再根”,如下:
void search(Tree root){
if(!root) return ;
search(root->left);
search(root->right);
cout<<root->data<<endl;
}
三种遍历的非递归实现(压栈):
search(root->left);
search(root->right);
cout<<root->data<<endl;
}
typedef struct TREE{
int data;
struct TREE *left;
struct TREE *right;
}tree,*Tree; //定义二叉树类型
stack<tree>s; //建立栈
void Search(Tree Root){
Tree root=Root;
while(!s.empty() || root!=NULL){
while(root!=NULL){
//cout<<root->data<<endl; //前序遍历入栈时输出
s.push(*root);
root=root->left;
}
*root=s.top();
//cout<<root->data<<endl; //中序遍历出栈时输出
s.pop();
root=root->right;
}
}
注:三种遍历的对二叉树的搜索方式是完全相同的,不同的是输出节点的次数不同,前序遍历第一次碰见就输出,中序遍历第二次碰见再输出,后序遍历第三次碰见才输出,后续遍历的压栈方式比较麻烦,与中序遍历的输出点相似,但需要判断后子树是否被遍历(或为空),若被遍历(为空),则输出,由此实现先输出右子树再输出父节点。
二叉搜索树:具有“父节点的值大于左儿子结点的值,小于右儿子结点的值”特性的二叉树称为二叉搜索树。
二叉搜索树的查找:
//递归实现
Tree find(Tree root,int data){
if(!root) return NULL; //返回为空
if(data>root->data){
return find(root->right,data); //查询右子树
}
else if(data<root->data){
return find(root->left,data); //查询左子树
}
else
return root; //返回地址
}
//循环实现
Tree find(Tree Root,int data){
Tree root=Root;
while(root){
if(data>root->data)
root=root->right; //查询右子树
else if(data<root->data)
root=root->left; //查询左子树
else
break; //相等,跳出循环
}
return root; //返回结果
}
二叉搜索树的插入:
//递归实现
Tree insert(Tree root,int data){
if(!root){ //若为空,将空地址变为结点
root=(Tree)malloc(sizeof(tree));
root->data=data;
toot->left=root->right=NULL;
}
else if(data>root->data){
root->right=insert(root->right,data); //插入右子树
}
else if(data<root->data){
root->left=insert(root->left,data); //插入左子树
}
return root; //返回当前结点地址
}
二叉搜索树的删除:
Tree deletetree(Tree root,Tree t){//返回值为删除后的根节点的位置
Tree tmp;
if(!root){ //未找到要删除的结点
cout<<"Not Find!"<<endl;
}
else if(t->data<root->data){ //小于根节点,从左子树删除
root->left=deletetree(root->left,t);
}
else if(t->data>root->data){ //大于根节点,从右子树删除
root->right=deletetree(root->right,t);
}
else{ //待删除结点找到,称其为根节点
if(root->left && root->right){ //左右儿子(子树)均存在
tmp=findmin(root->right); //找出右子树最小值的位置(左子树最大值也行)
root->data=tmp->data; //用右子树最小值覆盖待删除结点的值
root->right=deletetree(root->right,tmp->data);//删除右子树的最小值
} //主要思路是将删除目标结点转为删除叶子结点(最小值)问题
else{ //若只有一个儿子或没有儿子存在
tmp=root;
if(root->left){ //若为左儿子
root=root->left; //左儿子覆盖目标节点
}
else if(root->right){ //若为右儿子
root=root->right;
}
else{ //若没有儿子
root=NULL; //将目标结点设为空
}
free(tmp); //释放目标结点内存
}
}
return root; //返回根节点的值
}
平衡因子:左右子树高度差的绝对值;
平衡二叉树:平衡因子不大于1的二叉搜索树称为平衡二叉树,搜索二叉树的平衡性越高,它的查找效率越高;
堆:这个堆是数据结构里的堆,不是内存模型,堆栈中的那个“堆”,而是一个满足一定性质的完全二叉树,分为最大堆和最小堆。最大堆:堆中任意一个树的根节点的值是这个树中的最大值;
最小堆:堆中任意一个树的根节点的值是这个树中的最小值;
由于堆是一个完全二叉树,因此通过数组很容易实现,如下是堆的结构:
typedef struct Heap{
int *data; //堆的数组的指针,可为int,double等等
int size; //堆中元素当前的数量
int capacity; //堆的容量
}*MaxHeap,Heap;
MaxHeap CreatMaxHeap(int maxsize){
MaxHeap H=(MaxHeap)malloc(sizeof(Heap)); //创建一个堆
H->data=(int *)malloc((maxsize+1)*sizeof(int)); //申请堆中元素的空间
H->capacity=maxsize; //堆的容量
H->size=0; //当前元素的个数
H->data[0]=MaxData; //填入可能的最大值,作为哨兵,提高堆的插入效率
return H;
}
堆的插入:基本思路是将元素直接放在堆的最后,然后判断是否满足最大堆的特性(值小于其父节点),若不满足,则调整直到满足。
void insert(MaxHeap H,int data){
if(H->capacity==H->size){ //堆已满
cout<<"Heap is Full!"<<endl;
return ;
}
int i; //i表示插入点
i=++H->size; //起初i为最大
while(data>H->data[i/2]){//若data大于当前插入点的父节点
H->data[i]=H->data[2/i];//将父节点的值给当前插入点
i/=2; //将当前插入点指向父节点
} //循环判断当插入点为父节点时的情况
//由于哨兵的存在,当i=1时必然跳出循环,省去了判断i>1的条件,从而加快了算法效率
H->data[i]=data; //将数据插入
}
堆的最大值的删除:基本思路是用最后的那个值替补根节点的位置,再判断是否满足最大堆的特性,若不满足,调整直到满足即可。
int delete(MaxHeap H){
int Maxdata=H->data[1]; //将堆中最大值赋给maxdata
int tmp=H->data[H->size--]; //将最后一个值取出来
int parent,child //parent为最后一个元素应该放的位置,child为其儿子结点
for(parent=1;2*parent<=H->size;parent=child){ //初始默认为首位置,2*parent<=H->size表示存在儿子结点
child=2*parent;
//定位左右儿子中较大的那个儿子的位置
if(child!=H->size && //右儿子存在
H->data[child]<H->data[child+1])//右儿子值大于左儿子
child++;
//若tmp小于儿子结点值,则将儿子结点值上移
if(tmp>H->data[child]) break;
else{
H->data[parent]=H->data[child];
}
}
H->data[parent]=tmp; //将tmp放入合适位置
return Maxdata; //返回最大值
}
堆的建立与堆排序:
堆的建立:1.将已给数据按顺序放在堆中;
2.从最后一个非叶子结点开始进行调整,使其成为一个最大堆;
3.依次从下向上对所有节点进行调整,直到到达根节点,则此时的堆为最大堆。
//以parent为根节点的堆的调整
void adjustheap(MaxHeap H,int parent){
if(parent*2>H->size) return ; //若此节点为非叶子节点,不需要调整
int lchild=parent*2; //左儿子
int rchild=lchild+1; //右儿子
int max=parent; //用max指向父节点和两个儿子结点中最大的结点
if(lchild<=H->size && H->data[max]<H->data[lchild]){//若左儿子大于父,则指向左儿子
max=lchild;
}
if(rchild<=H->size && H->data[max]<H->data[rchild]){//若右儿子最大,则指向右儿子
max=rchild;
}
if(max!=parent){ //若max指向的不是父节点,则表示堆需要调整
swap(H->data[max],H->data[parent]);//交换父节点和最大儿子节点的值
adjustheap(H,max); //调整以最大儿子结点为根节点的树
}
}
void creatheap(MaxHeap H){
for(int i=H->size/2;i>=1;i--){
adjustheap(H,i); //从第一个非叶子节点开始调整
}
}
堆排序:堆排序是在堆的建立上,对其进行修正的过程,只需将堆顶(最大值)取出来与最后一个未交换过的值交换,然后依次循环,直到堆中所有数据都被排序。
void heapsort(MaxHeap H){
int size=H->size; //先将元素总数取出来
while(H->size>1){ //未排序元素总数大于1时进行排序
swap(H->data[1],H->data[H->size--]);//将最大值放在最后
adjustheap(H,1); //调整交换后的堆结构
}
H->size=size; //将元素总数重新给堆
}