华科考研834 2019
华中科技大学计算机考研试卷总结 2019(一) 834
- 空串和空白串不一样
- 串是一种特殊的线性表,又叫内容受限的线性表,它的内容往往只能是一个字符或者字符串。
- 单循环链表中设置尾指针比设置头指针更有优势
设尾指针rear,则访问末尾节点就是rear;
访问头结点就是rear->next-next就是第一个有效节点(首节点);
rear->next是头结点
__________________________________________________________
1. 带尾指针的单向循环链表最后一个节点之后插入或者删除一个节点时间复杂度O(1),在头结点之后删除或者删除一个节点的时间复杂度是O(1);若是采用头指针,对首节点进行操作的时间复杂度是O(1),但是对末尾节点进行操作,时间复杂度就是O(n);
2. 带尾指针的双向非循环链表,对尾结点进行删除或者插入操作时间复杂度是O(1),但是对首节点进行删除或者插入,它的时间复杂度就会变为O(n)
3. 带头指针的双向循环链表,找到最后一个节点的直接前驱和找到首节点的直接前驱都非常容易,时间复杂度都是O(1),但是双循环链表在使用中每个节点会有额外的空间开销,因此效率不如带尾指针的单循环链表,可以完成但不是最优解
4. 带头指针的单向循环链表,对首节点进行操作时间复杂度是O(1),但是对尾结点进行操作时时间复杂度是O(n)
____________________________________________________________
循环单链表默认是添加头结点的,末尾节点的指针域不是null,而是指向头结点
- 一维数组的\(n\)个元素,读取(访问)的平均时间复杂度是\(O(1)\) ,但是插入或者删除的时间复杂度是\(O(n)\) ;\(n\)个元素链表的随机读取时间复杂度是\(O(n)\) ,插入或者删除的时间复杂度是\(O(1)\)
讨论时间复杂度往往要考虑是读取(访问)的时间复杂度,还是插入或者删除的时间复杂度,结合是顺序表还是链表进行判定,有时候考虑综合时间复杂度时候,读取和插入删除这两部分都要考虑,但是考研试题以及大多数面试中,往往只考虑一个方面,要么是插入删除的时间复杂度,要么是读取访问的时间复杂度,尤其是前者 前面所讲的时间复杂度默认都是平均时间按复杂度
- 知道二叉树的中序遍历和前序遍历往往可以唯一确定这颗二叉树,方法是:前序遍历的第一个节点是整颗二叉树的唯一根节点,在根据这个根节点将中序遍历序列进行左右子树划分,然后对左右子树也进行类似划分,序列分块,找根节点
- rear是队列队尾元素指针,指针变量\(t\)指向将要入队列的指针\(X\) ,则入队操作是
//队列和栈一样,也是一个下面小,上面大的顺序元素 顺序表型队列
//链表型队列 就是一个带头结点的单向链表
//入队列操作
rear->next = t;
rear = t;
//出队操作
p = front;
front->next = front;
free(p);
- 有序单链表插入一个新的元素使得继续有序的时间复杂度是\(O(n)\)
先找到待插入的位置,时间复杂度是O(n),再修改指针,时间复杂度是O(1)
- 删除栈顶元素的指针操作
//栈也有顺序结构和链式结构两种
//顺序栈是下面序号小,上面序号大的线性数据结构
//顺序栈出栈
int x =top.data;
top--;
//顺序栈进栈
top++;
top.data = x
//链式栈出栈 对于链栈,栈底是next为空的那头元素
top = top->next
- 对图的遍历。广度优先遍历算法类似树的层次遍历 ,深度优先遍历算法类似树的先序遍历
- 基数排序不进行比较,其他排序都会进行比较和交换
排序方式大的可以分为五种,插入排序、交换排序、选择排序、归并排序、基数排序
插入排序:直接插入排序、折半插入排序、希尔排序
思想:每次将一个元素插入到前面已经排好序的序列中 这种排序算法的特性注定会是局部有序,即现在的有序也许会受到将来元素的插入而改变,不是正确排序的最终位置
直接插入排序
每次从当前插入元素依次向前比较,边比较边后移比较时在原地排序。空间复杂度O(1),时间复杂度是\(O(n^2)\) 。适用于线性表和顺序表。稳定排序
折半插入排序
基于直接插入算法。改进部分是:对于已经排好的顺序元素,使用折半查找算法查找出待插入元素位置,再统一进行后移腾出位置。时间复杂度\(O(n^2)\) ,稳定排序
希尔排序
适用于基本有序的情况,还是建立在基本选择排序方法之上。先设置一个增量(通常取n/2,向下取整)。依据增量形成比较多个子对,在子对内部使用选择排序,每一轮完成后,再取新的增量,\(di+1=di/2\) ,每一轮让增量d为原来一半。时间复杂度\(O(n^{1.3})\) ,最坏情况下的时间复杂度是\(O(n^2)\) 这个算法是不稳定排序,涉及到子对远距离交换,空间复杂度是$O(1) $ 仅适用于顺序表
交换排序:冒泡排序、快速排序
思想:根据序列中的两个元素比较结果,进行位置的交换 注定会是全局有序,某一状态的有序会一直维持到最终全部有序
冒泡排序 从后往前(或者从前往后也行),依次比较相邻两个元素的大小关系,进而得到位置关系,每一轮可以得出一个最终位置,会排\(n-1\)轮 稳定性:由于比较时使用的是\(>、<\) ,当两个元素相等时不会进行交换,因此是稳定算法。 空间复杂度\(O(1)\) ,时间复杂度\(O(n^2)\) ,最坏时间复杂度也是\(O(n^2)\)
快速排序 快速排序基于分治策略。快速排序是所有排序算法中性能最优的排序算法,并且每一趟排序都是全局有序,会放到其最终位置上。空间复杂度\(O(log2n)\) ,所有考试排序中唯一有的,时间复杂度:\(O(nlog2n)\) ,最坏时间复杂度\(O(n^2)\) ,不稳定的算法
先随机选一个中心主元pivot,所有比pivot小的数放到主元左边,所有比pivot大的数放到主元右边,这样得到的pivot位置是最终位置,和冒泡算法一样,每完成一轮比较,可以最终确定当前pivot的最终位置。这样的的一轮叫做一次快速排序或者叫一次划分
为了方便起见,每次都选择最左边的数字作为pivot
适用情况:每次选取的pivot可以把元素分成两部分,此时排序算法的速度最快,当已经是正序或者逆序就最慢 最大递归次数是n,最小递归次数是\(log2n\)
初始时,序列最左端和最右端各有一个指针,记为\(L\) 和\(R\) ,一般取首元素作为pivot(实际上pivot的选取是任意的),这时左边对应\(L\) 就是空的 \(R\) 是找比pivot小的数字,\(L\)是找比pivot大的数字,\(L\)和\(R\)重合的位置就是当前pivot可以放到的位置
\(08<pivot(19)\) ,所以把08移动到\(L\)位置指向的位置;
移动完\(R\) 指针,交替移动\(L\) (交替是只有发生事实上的移动才会有交替移动,当比较后发现不需要移动的时候,继续移动当前指针,不需要交替移动指针)
一直重复,直至\(L\)和\(R\) 重复,重复的位置就可以把当前pivot放到该处去
PS:比较以后一定要移动下标\(L\) 或者\(R\) ,至于是移动哪一个有以下规律:比较之后不发生移动的,继续移动上次移动的下标,向中间移动一次;比较以后发生移动的,先将该元素移动到对面方指针指向的空位置处,并且交替指针(使成为对面向指针),交替指针之后向中间移动一次
\(R\)指针是把小的元素移动到左边;\(L\)指针是把大的元素移动到右边
选择排序:简单选择排序、堆排序
选择排序的基本思想是:每一趟在待排序元素中选择最小的元素,作为有序序列中的元素
第\(i\)趟排序在\(L[i,i+1,…n]\) 中选择最小的元素
简单选择排序 时间复杂度\(O(n^2)\) 最坏情况下的时间复杂度是\(O(n^2)\) ,甚至于最好情况下的时间复杂度都是\(O(n^2)\) ,经过\(n-1\)轮选择。 每一次的选择都会是最终位置。是一种不稳定的排序算法
堆排序 利用完全二叉树的性质对数字进行排序
适用于待排序记录比较多的情况,虽然建堆复杂,但是毕竟性能高
大根堆:树以及子树必须满足父节点的内容大于子节点的内容
建堆: 从已有树的最底层观察自底向上观察是否满足大根堆的要求,若有不满足就进行heapify,使它最终调整成为大根堆,有时候上层节点调整堆会对下层原来排好的堆进行毁坏,因此建堆的时候下层的堆也要顾及得到,一直需要向下维护到叶节点才能保证成为一个完整的堆
一般对杂乱无章的数字进行heapify要从倒数第二层往上进行,从最小的子树进行heapify一直往上进行,让进行heapify的子树规模不断扩大,一直扩展到整个树都能进行heapify,这样全部节点上的数字就会有序
每次输出的元素只有一个,都在堆顶(这也是为什么堆排序归类为选择排序的原因),它和选择排序非常相像,大根堆就是每次选出大根堆对顶元素输出,即找出最大值输出,输出后对剩余元素重新进行heapify,继续找到当前节点中的最大值。过程和选择排序依次比较得出最大元素的过程非常像
树节点存到数组中,对数组中前n个所有元素进行heapify,找到最大元素后,放到数组最后边;再对前\(n-1\)个元素进行heapify,找到的最大元素放到数组中倒数第二个位置;一次重复进行heapify,知道得到每一轮堆的最大值
堆排序的平均时间复杂度\(O(log2n)\) ,最好时间复杂度\(O(log2n)\),最坏时间复杂度\(O(log2n)\) ,空间复杂度\(O(1)\)
比如对一个无序数字进行大根堆:
一、 先把数组中的元素全部填进完全二叉树
二、 从倒数第二层往上依次进行父节点、子节点的比较,调整成一个大顶堆
三、 堆的重建:移出根节点之后(堆顶),从这个剩余堆中选最后一个元素到堆顶 ,然后进行堆的调整(着重调整被影响的子树)使成为有一个大根堆;再次输出堆顶,继续调整剩余堆使得全部堆顶元素被输出
堆排序适合于待排记录比较大的情况下
归并排序
思想:归并的含义是将两个以上的子有序表 合并成一个有序表
将$L [1…n] $ 中长度是\(h\) 的相邻有序段合并成长度是\(2h\) 的相邻有序段,2路归并就是一次merge两个段序列。排序趟数是\(m\) ,进行\(k\)路归并,总共有\(N\) 个元素,满足\(N = k^m\)
平均时间复杂度\(O(nlog2n)\) ,最好时间复杂度,最坏时间复杂度都是\(O(nlog2n)\) ,空间复杂度是\(O(n)\)
稳定排序算法 且可以作为外部排序算法
每趟归并排序时间复杂度是\(O(n)\) 共进行了\(O(log2n)\)
基数排序
基数排序最特别,是根据一个数字的各位进行划分class。一般我们进行的是最低位优先基数排序。基数排序是稳定的排序算法
每次根据个位、十位、百位进行分类。一般我们都排十进制数,基数就是10,每两轮基数排序之间衔(''分配''收集'')接决定了无需考虑相对大小关系。或者说上一轮的收集和下一轮分配的紧密连接,决定了算法的成功
设进行d趟分配和收集,一趟分配是\(O(n)\) ,一趟收集是\(O(r)\) 所以基数排序时间复杂度\(O(d(n+r))\)