王道数据结构2020版本个人学习笔记(第一轮)持续补充中……

目录

第三章   栈和队列

3.1 栈

3.2 队列

3.3 栈和队列的应用

3.4 特殊矩阵的压缩存储

3.5 算法题遇到的一些问题

第4章 树与二叉树

4.1 树的基本概念

4.2 二叉树的概念

4.3二叉树的遍历和线索二叉树

4.4 树、森林

4.4.1 树的存储结构

4.4.2 树、森林与二叉树的转换

4.4.3 树和森林的遍历

4.5 树与二叉树的应用

4.5.1 二叉排序树

4.5.2 平衡二叉树

4.5.3 哈夫曼树和哈夫曼编码

4.6 树的应用题总结



第三章   栈和队列

3.1 栈

存储单元?

逻辑结构?

栈的基本操作?

C语言标识符?

上溢和下溢?

卡特兰数?

存储单元:一个存储单元可以存储一个字节(Byte=8bit),计算机的存储器容量是以字节为最小单位的,比如1KB的存储他有1024个存储单元,一个字母占一个字节,一个汉字占两个字节。一个字符等于两个字节。

逻辑结构:线性结构和非线性结构,指的是数据元素之间的逻辑关系,即从逻辑关系上描述数据,它与数据的存储无关,是独立于计算机的。

栈的基本操作:

InitStack(&S),StackEmpty(S),push,pop,GetTop(S,&x),ClearStack(&S)

C语言标识符:标识符的第一个字符必须是大小写英文字母或下划线

 上溢和下溢:栈的插入和删除操作都是在栈顶进行的,只可能栈顶指针超出了最大范围,从而产生上溢,而不是下溢。

卡特兰数:对于n个不同元素进栈,出栈序列的个数为

 

3.2 队列

1.队列的顺序存储是怎么进行进队操作和出队操作的?

进队操作:队不满时,先送值到队尾元素,再将队尾指针加1(注意先后顺序,题目中着重考察)

出队操作:对不空时,先取队头元素值,再将队头指针加1

注意:循环队列的出队操作和进队操作也是如此

2.用单链表实现队列时,队头在链表的什么位置?为啥子?

最理想的情况是一个循环单链表,带头结点,带尾指针。其中头结点头指针负责删除,尾指针负责增加,那么入队和出队的时间复杂度都为O(1),这时我们再来看队头指针就应该在链头了。

 

3.3 栈和队列的应用

1.页面替换算法是否用到队列?

是的,在FIFO,先进先出中用到队列

2.栈在表达式求值中,一般是用于存放操作符的。

3.执行函数的时候,局部变量一般采用什么结构进行存储?

栈结构,因为调用函数的时候,系统会为调用者构造一个由参数表和返回地址组成的活动记录,并将记录压入系统提供的栈中,若被调用函数有局部变量,也要压入栈中。

注意:在递归调用的过程中,系统为每一层的返回点,局部变量,传入实参等开辟了递归工作栈来进行数据存储。递归调用函数时,在系统栈中保存的函数信息需满足先进后出的特点。

 

3.4 特殊矩阵的压缩存储

1.三角矩阵A中的元素按行优先存放在一维数组B中,aij元素对应B中数组下标K应当是?

K=2(i-1)+j-1

若已知K,求i和j?

i=(k+1)/3+1,j=k-2i+3

2.什么是稀疏矩阵?拿来干嘛?

矩阵元素个数S远远大于非零个数t,S>>t的矩阵称为稀疏矩阵,用来存放非零元素,节省存储空间,稀疏矩阵压缩存储后便失去了随机存取特性。

3.什么是十字链表?(这部分引用了青岛大学的课程教学资料)

概念:十字链表是有向图的另一种链式存储结构。我们也可以把他理解为有向图的邻接表和逆邻接表的结合。有向图中的每一条弧对应十字链表的一个弧节点,每一个顶点对应一十字链表的一个顶点节点。

举个例子:

如这样的有向图,我们的十字链表应该怎么画勒

先来看看邻接表是怎么画的:

邻接表描述了每个点的出度节点,但是有个缺点是,要想知道一个点的入度节点的话,则需要我们遍历整个邻接表,所以我们引入了十字链表。

现在修改表头节点的指针域,分别是数据域,入度边,出度边

修改弧节点的指针域,分别是,弧尾,弧头(带箭头的那端指向的那个节点),弧头一样的下一条弧,弧尾一样的下一条弧

画十字链表的步骤是:先把所有的出度边画出来,然后再来画入度。

 

3.5 算法题遇到的一些问题

1.为什么在关于一些栈,队列,链表函数传递参数要用到&,而有些又不用?什么时候用?

取地址,我们就可以对这个变量进行操作,改变这个变量。不用&,就只能得到副本,可以看,但是不能动。一句话来说,当你要改变这个变量,就加取地址,不想改就不加。

2.数据结构里面的基本操作一般是可以直接用的,下面罗列一下:

(1)栈的基本操作:                                                (2)队列的基本操作

InitStack(&S)                                                                InitQueue(&Q)

StackEmpty(S)                                                              QueueEmpty(Q)

Push(&S,x)                                                                    EnQueue(&Q,x)

Pop(&S,&x)                                                                  DeQueue(&Q,&x)

GetTop(S,&x)                                                                GetHead(Q,&x)

ClearStack(&S)

 

3.!是取非运算,都快忘了。比如stackEmpty(S)是判空,假设是true,那么!stackEmpty(S)之后就是false了。

4.指针的问题:

算法题不建议用指针,首先王道书上的算法答案本身就不一定是可运行的代码,保险起见能用数组就用数组吧。

char *str=“abc”和char str[]="abc"有什么区别嗷?

前面只能看,不能改。后面可以改。

 

5.算法题更注重的是逻辑清晰,而不是代码一定要可运行。一定要把备注写清楚。因为题目问也只是问个逻辑,而不是编程比赛。


第4章 树与二叉树

4.1 树的基本概念

1.树的定义是递归的,是一种递归的数据结构。树作为逻辑结构,同时也是一种分层结构。

 

2.树的路径长度怎么求?

是从树根到每个结点的路径长度的总和

 

3.常用于求解树结点与度之间关系的有:

总结点数=n0+n1+n2+……+nm

总分支点数=1n1+2n2+……+mnm

总结点数=总分支数+1

 

4.2 二叉树的概念

题目中涉及到的知识点(主要是我个人不熟悉知识点)

1.二叉排序树的插入和删除?

插入:

若二叉排序树为空,则插入结点作为根节点插入到空树中

否则,继续在左右子树中查找

树中已有,则不再插入

树中没有,则查找至某个叶子节点的左子树或右子树为空为止,则插入。结点应为该叶子结点的左孩子或右孩子

删除:

(1)被删除的结点是叶结点时,直接删除该结点

(2)被删除的结点只有左子树或者右子树,用其左子树或者右子树的结点替代他即可

举例:

(3)被删除的结点既有左子树又有右子树的时候

A.以其中序前驱值替换之(值替换),然后再删除该前驱结点。前驱是左子树中最大的结点

B.也可以用后继替换之,然后再删除该后继结点。后继是右子树中最小的结点。

选择AB的策略,主要是看树的高度,选择变换后树的高度最小的那一个。

 

2.树如何转化为二叉树?

将树转换成二叉树的步骤是:
(1)加线。就是在所有兄弟结点之间加一条连线;
(2)抹线。就是对树中的每个结点,只保留他与第一个孩子结点之间的连线,删除它与其它孩子结点之间的连线;
(3)旋转。就是以树的根结点为轴心,将整棵树顺时针旋转一定角度,使之结构层次分明。

 

3.为什么在m叉树的情形下,结点i的第一个子女编号为j=(i-1)*m+2

 

 

4.3二叉树的遍历和线索二叉树

1.先序遍历的非递归算法?

思想:边遍历边打印,结点顺序是中左右

代码:

void PreOrder(BiTree T){
  InitStack(S);BiTree p=T;//初始化栈,构造遍历指针P
  while(p||!IsEmpty(S)){//当p不为空,S不为空时
    if(p){//如果p不为空,就将该点输出,且拜访他的左子树
         visit(p);
         push(S,p);
         p=p->lchid;
         }
    else{//如果p的左子树遍历完了,就开始回访以前的结点中哪些右子树是还没输出的,依次输出
        pop(S,p);
        p=p->rchild;
         }
                         }
                         }

2.中序遍历的非递归算法:

思想:找到左子树最下边的结点,并同时要用栈保存根节点,因为要通过根节点来进入右子树。结点访问顺序,左中右。

代码:

void InOrder(BiTree T){
  InitStack(S);BiTree p=T;
  while(p||!IsEmpty(S)){
    if(p){
      push(S,p);
      p=p->lchild;  
      }
    else{
      pop(S,p);
      visit(p);
      p=p->rchild;
      }

  }
}

3.后序遍历的非递归算法:(等题目具体涉及到了再来更新)

思想:要先访问完左孩子和右孩子之后才能访问根结点。如果根结点没有孩子,当然就直接输出;如果有,并且已经访问过左孩子和右孩子结点就可以访问根结点;如果根结点有孩子,但是孩子还没访问,就依次让右孩子和左孩子入栈,这样就保证了左孩子在右孩子之前访问。

代码:

void PostOrder(BiTree T){

InitStack(S);
BiTree p;       //当前结点
BiTree pre=null;//前驱结点
push(T,S);//首先栈里面要存放根节点

while(!IsEmpty(S)){
p=S.top();//p的值为当前栈顶元素
if((p->lchild==null&&p->rchild==null)||
   (pre!=null&&(pre==p->lchild||pre==p->rchild)){//如果p没有孩子结点,或者p的孩子结点已经访问过
    visit(p);
    pre=p;
                                                 }
else{
    if(p->rchild!=null)push(S,p->rchild);//让右孩子先入栈
    if(p->lchild!=null)push(S,p->lchild);
}
}
}





}



}

4.层次遍历:

思想:先进先出,先把根节点入队,然后出队并访问该结点,再把该结点的左子树和右子树结点入队。然后出队,对出队结点访问,如此反复。

代码:

void LeverOrder(BiTree T){
  InitQueue(Q);
  BiTree p;
  EnQueue(Q,T);//将根结点入队
  while(!IsEmpty(Q)){
    DeQueue(Q,p);
    visit(p);
    if(p->lchild!=null){//如果左子树不为空,将其入队
    EnQueue(Q,p->lchild);
    }
    if(p->rchild!=null){
    EnQueue(Q,p->rchild);
    }
}

}

 

5.可以唯一地确定一棵二叉树是哪些序列?

先序中序,中序后序,层序中序。

6.通过中序遍历对二叉树线索化的递归算法

思想:对二叉树的线索化,实质上就是遍历一次二叉树,只是在遍历的过程中,检查当前结点左右指针域是否为空,若为空,将它们改为指向前驱结点或后继结点的线索。

代码:

void InThread(ThreadTree &p,ThreadTree &pre){//p是当前结点,pre是上一个结点
    if(p!=null){
    InThread(p->lchild,pre);//递归访问左子树

    if(p->lchild==null)//如果左孩子为空,就指向前驱结点
        p->lchild=pre;

    if(pre!=null&&pre->rchild==null)//如果前驱结点不为空,且前驱结点右孩子为空
        pre-rchild=p;//就让前驱结点指向后继结点,即pre指向了p
  
    pre=p;//记录前驱结点
    InThread(p->rchild,pre);//递归访问右子树
    }
}

7.线索二叉树的遍历

void InOrder(ThreadNode *T){//线索二叉树的中序遍历
for(ThreadNode *p=Firstnode(T);p!=null;p=Nextnode(p))
    visit(p);
}

ThreadNode * Firstnode(ThreadNode *p){
while(p->Itag==0)p=p->lchild;//如果有左孩子,就一直找到最左的哪一个,也就是中序的第一个结点

return p;
}

ThreadNode * Nextnode(ThreadNode *p){
if(p->rtag==0) return FirstNode(p->rchild);//如果没有线索,就找到右孩子树的最左结点

else return p->rchild;//否则就是有线索,返回后继元素即可 
}

 8.线索二叉树是一种什么结构

物理结构。

9.为什么后序线索二叉书中求后序后继问题仍不能有效解决。

随便举个例子,找一个不是根节点且带有右孩子的结点,当求后序后继结点时,由于右孩子不为空,所以根本无法找到后继结点。

10.后序遍历为什么需要栈的支持?

因为当结点x的右孩子不为空时,右指针无法指向其后继,所以通过指针可能无法遍历整棵树。所以还是需要栈的支持的。

 

 

4.4 树、森林

 

4.4.1 树的存储结构

 

4.4.2 树、森林与二叉树的转换

二叉树=>树:左孩,右孩连双亲,去掉原来右孩线

树=>二叉树:兄弟相连留长子,绕根结点旋转45°

森林=>二叉树:树变二叉根相连

二叉树=>森林:去掉全部右孩线,二叉再还原

二叉树转换成树的时候,记住二叉树的结构是左孩子右兄弟

 

4.4.3 树和森林的遍历

 

 

4.5 树与二叉树的应用

 

4.5.1 二叉排序树

 

4.5.2 平衡二叉树

 

4.5.3 哈夫曼树和哈夫曼编码

1.为什么哈夫曼编码能够保证是前缀编码?

因为没有一片树叶是另一片树叶的祖先,所以每个叶结点的编码就不可能是其他叶结点编码的前缀

2.为什么哈夫曼编码能够保证字符编码总长最短?

因为哈夫曼树的带权路径长度最短,故字符编码的总长最短

性质一:哈夫曼编码是前缀码

性质二:哈夫曼编码是最优前缀码

注意:哈夫曼树并不一定是二叉树,度为m的哈夫曼树只有度为m和度为0的结点。

3.若度为m的哈夫曼树中,叶子结点个数为n,则非叶子结点的个数为多少?

首先回忆一下公式:

总结点数=n0+n1+n2+……+nm

总分支点数=1n1+2n2+……+mnm

总结点数=总分支数+1

回答:

设结点总数为S,   度为m的结点个数设为x

S=x+n;

具有S个结点的哈夫曼树有S-1条分支

S-1=xm

x+n-1=xm

x=(m-1)/(n-1)

 

4.6 树的应用题总结

1.什么是最佳二叉排序树?怎么构造他?

最佳二叉排序树就是指高度最小的二叉排序树,构造方法就是把给定的序列从小到大排序,然后用二分法,每次去中间值。当然取中间偏左的值还是中间偏右的值都可以。(具体实现比较简单,大致看一遍就可以了,此处不举例)

2.什么是大根堆

大根堆要求根节点的关键字值大于等于左右子女的关键字值,且大根堆是一棵完全二叉树

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值