数据结构期末极限复习

选择题填空:书本课后题
//我的代码可能是错的,没有经过ide运行,直接写的


程序填空题:顺序表插入
    统计二叉树结点个数
    堆排序(调整堆)

顺序表插入(简单):
//课本代码
public void insert (int i , Object x) throws Exception
    {
        if (curLen == listElem.length)
            throw new Excetpion("顺序表已满");
        if (i<0 || i>curLen)
    /:        throw new Excetpion("插入位置合法");
        //核心代码,就是后移嘛
        for (int j = curLen; j>i, j--)
            listElem[j] = listElem[j-1];
        listElem[i] = x;
        curLen++;
    }

统计二叉树结点个数:
//自己打的,遍历左子树右子树加上根结点就是总结点数了
protected int rcountLeaves(BinNode<Elem> root) {
        if (root == null) {
            return 0;
        }
        if (root.right == null && root.left == null) {
            return 1;
        }
        return rcountLeaves(root.left) + rcountLeaves(root.right);
    }

    public int countLeaves() {
        return rcountLeaves(root);
    }

    public int countNodes() {
        return countLeaves() + 1;
    }

//核心代码:percolateDown
/*如果考的是二叉堆(用完全二叉树储存数据的堆)的话就是下面的代码*/
//用数组实现,下面考虑小顶堆的操作(代码自己打的)
//堆的调整核心操作就两个:向下过滤 向上过滤
//为什么要过滤?因为插入/删除的操作会改变堆的结构
//为了保持堆的结构,我们需要向下向上过滤
//当插入一个元素的时候我们需要percolateup,往上过滤
//当删除一个元素的时候我们需要percolatedown,往下过
//过滤可以看作是一个比较的过程,这个词比较拗口
//实践发现当数组从1开始保存数据时,i/2为父亲位置,i*2是左儿子
//i*2+1就是右儿子
//当数组从0开始保存,父亲(i+1)/2 - 1,左 2* i + 1
//堆排序全部代码:(c++.. 都差不多的)
#include<stdlib.h>
#include<cstdio>

using namespace std;

/*写在最前面:我们所学的是二叉堆(用完全二叉树(数组)储存)
  但是我们还有二项堆、Fib堆,这两种堆就不是用完全二叉树实现的
  二叉堆只是prorityqueue这个结构实现的一种方式,而不是说优先队列等同于二叉堆
  priortiyqueue还能用二项堆、avl树、线段树还有二进制分组的vector实现 
*/ 
/*
堆的其他操作:
全部操作都首先要看这个堆是最大堆还是最小堆,细节上有些差异 
1.提高元素优先级:提高优先级 + percolateup(因为提高优先级之后,这个位置不合适了,向上过滤) 
2.降低元素优先级:降低优先级 + percolatedown(和提高优先级的的理由一致) 
3.删除元素(不是堆顶的元素): 欲想让其灭亡,先让其膨胀,不是堆顶就提高它的优先级变成堆顶,那么我们的操作就变成了删除堆顶hh 
*/

/*删除操作:堆顶元素为最大或者最小 所以删除显然是删除堆顶比较方便
  本测试程序中以大顶堆为示例,删除最大的元素 
  考虑堆是空的时候,抛出异常 
*/
/*如果是删除堆顶,会导致堆顶空了一个位置,接着堆顶这个空的位置会与它的
  左儿子和右儿子进行交换,把这个空位给交换下去,导致一块地方给空了出来 
  最终会破坏掉整棵完全二叉树的结构
  (二叉树的深度为k,除第 k 层外,其它各层 (1~k-1) 的结点数都达到最大个数,第k 层所有的结点都连续集中在最左边,这就是完全二叉树) 
只要删除的是最后一个元素我们的结构就不会发生变化了,那么我们只需要删除堆顶的值而不删除它的位置,怎么做?
将最后一个元素的值和堆顶元素的值交换一下,然后堆顶向下渗透完事。
*/
 
//渗透是一个动作,没什么特殊含义(我自己的观点),就往下进行元素交换交换去调整元//素排列顺序
//从而符合二叉堆的结构 
void percolateDown(int k, int* array, int size){
    //向下渗透
    int temp;
    temp = array[k];
    int i, child;
//i要用来找要删掉的位置,所以要设在for外面
//i*2 + 1 < size 表示左儿子存在 
    for(i = k; i*2+1 < size; i=child){
        //左儿子 
        child = i * 2 + 1;
        //child+1右儿子 
    //现在做的是升序排列,每次找最大的那个出堆然后放最后面去
    //所以判断条件找大的 
    //下面判断右儿子是不是比左儿子更大 因为最大位置在size - 1
    //接上一句,所以child+1不会在child < size - 1的情况下越界
        if(child != size - 1 && array[child+1] > array[child]){
             child ++;
        }
    //堆顶比它小就要去挪数据 
        if(temp < array[child]){
            array[i] = array[child];
        }
    //else 找到一个大于等于的就退出不用动了 
        else break;
    }
    array[i] = temp;
} 

/*Heap排序的实质:假设堆里有n个元素,连续删除n次堆顶(存下来)就是排序了
    因为每次删除堆顶的时候都会对这个堆进行调整,保证删的元素是最大or最小
    时间复杂度上:删除一个元素的时间复杂度是logn,还要进行n次,所以就是nlogn 
    那么我们需要一个另外的n元数组来储存出堆的元素
    而实际上,由于我们每次删除堆去调整堆的时候,会用一个下面的元素去顶替堆顶
    此时,那个用来顶替堆顶的元素的位置会空缺的,也就是说我们能借用这个地方来缓存我们删除的堆顶 
    这样子的话,小顶堆在不断进行删除之后我们就会得到一个升序的序列 
*/
void heapSort(int* array, int size){
    /*我们只是应用堆排序的思想,不必要真的将数组元素拷进堆里再排序再返回
      甚至删除我们也不需要,因为删除只要放进最后一个就好了,我们只需要用到percolatedown 
      新的堆排序应用在数组上,数组以0开始,现在要改变寻子寻父的
      公式,左儿子变成了2*i+1,而右儿子邻接左儿子,所以右儿子是2*i+1+1 
      原先树根从1开始,左右儿子:左儿子能被2整除,右儿子余1所以都能直接/2找到父亲
      现在树根从0开始,在不断实践中发现,把这个1加回去再除以2,然后再减掉1
      (i+1)/2 - 1这个公式就都符合左右儿子找父亲的公式 
    
      percolatedown 中找最后一个拥有子节点的父节点
      那么现在数组从0->size-1,size-1就是最后一个节点的位置
      那么(size-1 +1)/2 - 1 = size / 2 - 1就是父亲了 当前的点向下过滤完后,自减找前面一个点进行过滤 
      pps:谨记这里的元素节点是有顺序的,所以自减就能找到前面一个点 
      负数的地方已经越界,不能再向下过滤,所以i>=0 
    */
    for(int i = size/2 - 1; i >= 0; i --){
    //for循环作用:调整元素排列顺序进行建堆(抽象意义上)
    //从最后一个有儿子的点开始不断向下过滤
        percolateDown(i, array, size);
    } 
    //把堆顶元素和最后一个做一下交换,然后进行向下过滤,一共要交换n-1次(n个元素前提下)
    //i=0时候已经全部出堆了,不需要等于0 
    for(int i = size - 1; i > 0; i --){
        int temp = array[size - 1];
        array[size - 1] = array[0];
        array[0] = temp;
    //拿出去一个元素之后,堆在变小,比如拿出来一个之后就是size-1,所以堆的size和i一致 
        percolateDown(0, array, i);
    } 
} 

void printArray(int * array, int size){
    for(int i = 0; i < size; i ++){
        printf("%d ",array[i]);
    }
    printf("\n");
}
int main(){
//pppps:原始序列有序,堆排序照样要找最大的,不会(正/负)影响它的效率 
    int array[6] = {11, 2, 33, 44, 55, 66};
    printArray(array, 6); 
    heapSort(array,6); 
    printArray(array, 6);
    return 0;
}
 
 
问答题:   排序
    最小生成树
    建立BST
    Hash线性探测
    DFS BFS
排序:自己看书
最小生成树:看书
BSTree:记住左子树、父亲、右子树 三个点的数值是从小到大
Hash线性探测: 有手就行
DFS BFS:DFS用栈 BFS用队列(代码参照蓝桥杯复习https://www.cnblogs.com/GDUFdebuger/p/13829462.html

综合题:1.根据前中后序遍历 画出二叉树 转成森林
             2.带头结点单循环链表合并成一个单链表

综合题解题方法:
1.根据前中后序遍历的特点(根节点位置、左右子树的遍历顺序)即可划分区间 注意边界即可
前序遍历:先根节点,后左子树,在右子树
中序遍历:先左子树,后根节点,再右子树
后序遍历:先左子树,后右子树,再根节点
前 1 2 4 5 3 6 74 5 2 6 7 3 1
前:1为root
       245为左子树
       367为右子树
后:   1为root
        452左子树
         673右子树 
再递归划分 就不说了
    1
             2    3
          4   5 6  7
其他组合同理
中序 9 3 15 20 7
后序 9 15 7 20 3
3为root
    3
            9      20
                  15   7
树转换成二叉树: 左孩子右兄弟
二叉树转森林:按顺序连root即可
//代码:思路,遍历数组找根节点划分左右子树区间,递归递归递归
//代码肯定不会全部让人手写,写不完的
//填空照着上面思路吧- - 

2./*获取两个链表的最后一个node p1 p2
    后赋值给s1 s2 
     s1连第二链L2的头结点,s2连第一链L1的头结点
*/
    Node p1 = new Node();
    Node p2 = new Node();
    Node s1 = new Node();
    Node s2 = new Node();
    p1 = L1.Head->next;
    p2 = L2.Head->next;
    //获取最后一个结点
    while(p1 != L1.Head){
               if(p1->next == L1.Head){
        s1 = p1;break;
    }
         p1 = p1->next;
    }
    while(p2 != L2.Head){
    if(p2->next == L2.Head){
        s2 = p2;break;
    }
    p2 = p2->next;
     }
     //合并
    s1->next = L2.Head->next;
    s2->next = L1.Head;
}

 

posted @ 2021-01-08 13:31  WriteOnce_layForever  阅读(470)  评论(0)    收藏  举报