数据结构与算法总结与易错分析

排序算法总结和易错点分析

冒泡排序

  • 冒泡排序有两层循环,外面的循环是记录循环次数而已,我建议用i=1来算次数,这样更容易理解,如果写成i=0当然也行容易加大思维负担

  • 还有一点,我有时候写算法时候,写着写着一不留神就把冒泡的交换

    int temp = arr[j];
    arr[j] = arr[j+1];
    arr[j+1] = temp;
    

    写成了如下

    int temp = arr[j];
    arr[j] = arr[j+1];
    arr[j+1] = arr[j];
    

    这个稍微留心一下习惯

  • 最后方法类型是int[]别忘了返回数组

快速排序

  • 快排的思想容易记偏,快排最重要的是中轴,记住重要的并不是找中轴,而是摆放中轴,中轴很好找,就是直接令最后一个元素就是中轴,所以重点根本就不是找中轴,而是怎么把中轴摆放到这个序列中间的某个位置,使得中轴左边的数都比中轴小,中轴右边的数比中轴大。之后便是左递归和右递归了
  • 快排怎么排出中轴呢,通过多轮交换,每一轮交换只涉及到左边换一次,右边换一次
  • 最外层 while 循环条件尽量写成left < right,这是因为要保证 left 等于 right 时候可以出循环,所以我们要注意里头的条件要加上left < right,包括左遍历遍历,右边遍历,这样里头的循环也可以在left = right时候或者 left 还没到 right 的时候可以出来,就不会发生left > right比较难处理的现象了
  • 里头循环我们无需关注和中轴相等的元素,不管是左边还是右边,都可以无需关注
  • 循环都结束后,我们把中轴放在 left 处或者 right 处,因为 left 和 right 从循环来的时候就已经相等了

简单选择排序

  • 外层循环代表经过几轮,可以从i = 1开始
  • 内层循环找到最小(大)值之后表下标,我建议从小到大排序,每个最大的排在最后面,符合思考习惯一点,然后交换元素可以直接用最外层循环的变量

堆排序

  • 堆排序要记住先将无序序列创建为最小堆或者最大堆,然后再把最小堆或者最大堆的根和尾部替换位置,这样就把最小或者最大的元素放到了最末尾,然后将根部元素下沉,下沉到不能再下沉为止,这样就又构造出一个新的最小堆或者最大堆,然后不断循环这样的操作,直到序列变成从大到小排序或者从小到大排序。简化来说就是,创建堆,然后循环的将根和尾部替换,最后就可以完成排序。如果创建最大堆,就是从小到大排序,如果是创建最小堆,就是从大到小排序
  • 如何创建大(小)顶堆?将一个无序的完全二叉树,从最后一个非叶子结点开始进行下沉操作,然后循环到根结点

直接插入排序

  • 直接插入要注意将后面元素插入到前面一个有序序列中时候什么时候出来,只有当不满足某个循环条件时候出来,再插入下一个元素,怎么插入呢?从有序序列最后一个开始不断后移即可

折半插入排序

  • 折半插入排序和直接插入排序很像,里头用到了折半查找的思想,折半查找注意循环条件,我喜欢设置循环条件当 left = right 时候出循环,再比较下元素是必这个位置元素大还是小即可

希尔排序

  • 里面是直接插入排序,外层进行缩小增量,缩小增量是有意义的,因为我们知道直接插入排序当元素基本有序的时候,排序的时间复杂度就很小(内层循环几乎不执行),所以缩小增量就是起了这个意义,希望通过某种操作先让元素基本有序,怎么做呢,缩小增量,我们假如使用朴素的希尔排序,开始增量为数组长度的一半,之后不断缩小为一半,一直到一,一其实就是常规的直接插入排序了,假如一个开始增量是 n,那么这里需要一个循环,增量为 n 的时候有 n 组数据可以进行插入排序,那么又可以写一个循环为表示这 n 组,里头再写一个直接插入即可,这样会发现是是 4 个循环,网上有的 人写 3 个循环,这是因为他们把第二个和第三个和在一起了,我喜欢分开,因为这样可以很清晰的表示分组进行插入排序,不分开表示,则是这 n 组交替的循环

数据结构总结和易错点分析

链表

  • 单链表的创建,头插法和尾插法,头插法最简单,尾插法可以先让 head 表示最尾部,然后我们每次插入新结点,往尾部插,然后再把最后结点赋给尾部即可
  • 不管是单链表还是双向循环链表的插入,我们都遵守一个这样的规律,那就是率先把要插入的结点的左边和右边连在表上,然后我们再去考虑动表中的连接,先动后面再动前面

  • 只用注意一点,不管是单链表还是栈,我们都把头结点作为一个未存数据的结点,因为方便我们指向后续结点

队列

  • 队列最开始时候 head 等于 tail 且指向空,现在创建的时候我建议 tail 往后移动一下再存,而不是存了之后再往后移,我们始终应该要保持 head 为一个空的结点指向的是存在的结点的形式
  • 顺序循环队列有几种方式来做,要么多设置一个记录数量的变量,要么牺牲一个数组中的空间,存我们都是 tail 存了之后,tail 后移,如果用一个计数变量的话,队空队满的条件都是head==tail,能保证数组空间都被使用,如果我们牺牲数组空间的话,队空的条件是head==tail,队满的条件是(tail+1)%arr.legnth==head,即 tail 下一个元素是 head 的时候表示队满,要知道当前这个 tail 所指的空间还是一个空的,这就相当于牺牲了一个数组空间。head 后移是(head+1)%arr.length,tail 后移是(tail+1)%arr.length

二叉树

  • 遍历的递归很容易,其中层次遍历是 BFS,BFS 一般不用递归形式,DFS 可以自然想到递归的形式,二叉树的非递归遍历算法,对于先中后序遍历采用栈,层次遍历采用队列。
  • 后序遍历二叉树的非递归算法比较麻烦,后序与先序和中序不同的是,只有当左右孩子都找过了之后,才能将根结点抛弃,这就会导致一个问题,假如一个结点左右都找过都不存在,那么我们把这个结点数据输出,并从栈中弹出这个结点,然后栈顶又变成父节点,若刚刚弹出的结点是左结点还好,因为下一个是右结点,若刚刚弹出的是右结点,那么父节点找左结点找不到又会找右结点,这样又是刚才那一步了,所以我们需要增加一个变量,来保证只有当又结点不存在并且右结点不是刚刚访问过的结点时候,才能指向右边,否则继续弹出
  • 层次遍历二叉树粗略的理解可能是一层一层的遍历,实际上这句话是有问题的,不是一层一层的遍历,按照这句话的理解就成了循环里每个循环中要遍历完一层,其实并不是这样,而是说按照从上到下,从左至右的顺序遍历,每次循环中只需要控制一个结点出队就行了,而不是该层所有节点出队,一旦一个结点出队了,同时就需要它的左右孩子结点入队
  • 复制二叉树,求二叉树深度,二叉树叶子结点数,非叶子结点数都可以用递归很方便的求解
  • 线索二叉树的创建,用先中后序递归来创建,注意有一个 pre 是全局变量,表示前驱,代码中有前驱指定与后继指定,涉及到两个元素,分别是当前元素和 pre 指向的元素,二者之间就可以写后继和前驱了
  • 线索二叉树的遍历,后序遍历很麻烦,可以尝试用左右根的方式写一下,很难写出,最后要转化为根右左的形式,也就是传统后序的倒序,这个根右左就和先序很类似了,在按照先序的思想来写就行了
发布了197 篇原创文章 · 获赞 62 · 访问量 7万+

猜你喜欢

转载自blog.csdn.net/abcnull/article/details/104741533