数据结构期末极限复习
选择题填空:书本课后题 //我的代码可能是错的,没有经过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 7 后 4 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; }