很多算法都一知半解,就是现在,一个个击破它们吧!
链表算法
反转单链
1 | //单链表的转置,循环方法 |
链表倒数第k个节点
设置两个指针 p1、p2,首先 p1 和 p2 都指向 head,然后 p2 向前走 k 步,这样 p1 和 p2 之间就间隔 k 个节点,最后 p1 和 p2 同时向前移动,直至 p2 走到链表末尾
1 | public class ListNode { |
速学路径
栈
数组方式
1 | public class StackArray{ |
链表方式
1 | public class LinkedStack<T> implements Stack<T>{ |
参考路径
串
字符串逆转
数组方式
1 | public static String strReverseWithArray2(String string){ |
栈方式
1 | public static String strReverseWithStack(String string){ |
参考路径
http://www.cnblogs.com/JohnTsai/p/5606719.html
遍历
二叉树遍历
1 | public class Node { |
查找
折半查找
搜索过程从数组的中间元素开始,如果中间元素正好是要查找的元素,则搜索过程结束;如果某一特定元素大于或者小于中间元素,则在数组大于或小于中间元素的那一半中查找,而且跟开始一样从中间元素开始比较。如果在某一步骤数组为空,则代表找不到。这种搜索算法每一次比较都使搜索范围缩小一半。
1 | // while循环 |
二叉查找树
- 若任意节点的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
- 若任意节点的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
- 任意节点的左、右子树也分别为二叉查找树;
- 没有键值相等的节点。
排序
升序是从小到大
降序是从大到小
速学路径
http://www.jianshu.com/p/bbb7c838880f
插入排序
说白了就是循环一遍,每个元素都按大小插到前面的序列里
1 | public static void insertSort(int[] list) |
希尔排序
有间隔的直接插入排序
1 | public void shellSort(int[] data) |
堆排序
堆是一颗完全二叉树,除了最底层之外,每一层都是满的。
它有最大堆和最小堆之分
堆中每个父节点的元素值都大于等于其孩子结点的堆就是一个最大堆
那么相反,每个父节点都小于等于其孩子结点的堆就是一个最小堆
堆排序的时间复杂度为O(n*logn)
他的排序方法就是先形成一个堆
父节点和两个子节点进行比较,把最大的那个换到父节点上,然后那两个子节点就递归这个比较和交换
然后开始排序了
把堆顶的元素拿下,并且把最小的元素换到堆顶,然后形成一个新堆,然后循环这样的操作,取出来的元素就有序了
满二叉树:除了叶结点外每一个结点都有左右子叶且叶结点都处在最底层的二叉树;
完全二叉树:只有最下面的两层结点度小于2,并且最下面一层的结点都集中在该层最左边的若干位置的二叉树;
堆是一种完全二叉树或者近似完全二叉树
1 | public static void sort(int[] a){ |
归并排序
将两个已经排序的序列合并成一个序列的操作
1 | public int[] gb_sort(int[] nums, int low, int high) { |
空间复杂度
平均时间复杂度是logn,所以根据遍历次数,推出空间复杂度也是logn
快速排序
从需要排序的数里面随便找出一个,然后,把比这个数小的放在这个数左边,比这个数大的放在这个数右边,一样大的和这个数放在一起,最后,左右两边各自重复上述过程,直到左边或右边只剩下一个数(或零个数)无法继续为止。
取出第一个元素为基准,然后有两个指针,一个指向第二个元素,一个指向最后一个元素,先从最后一个进行比较,如果比基准大的话,就向前移向下一个,如果元素比基准小的话,就把那个元素替换到前面那个空位,然后前面的指针开始比较,如果比基准小,就移向向后下一个,如果比基准大,就替换到后面的那个空位。然后又变成后指针开始移动比较。这样的比较替换后,除去基准的那个元素,再分成前后两组,然后进行这个过程,直到前后两个指针重合,排序也就结束了。
快速排序不稳定,它的时间复杂度最好时为O(nlogn),最差情况是O(n\n)
1 | public int partition(int[] arr, int start, int end){ |
优化
三值取中法: 待排序序列的前(第一个位置)、中(中间位置)、后(最后一个位置)三个记录中的中间值(按大小排序)作为枢轴
空间复杂度
就空间复杂度来说,主要是递归造成的栈空间的使用,最好情况,递归的深度为log2n,其空间复杂度也就为O(logn),最坏情况,需要进行n‐1递归调用,其空间复杂度为O(n),平均情况,空间复杂度也为O(logn)。
冒泡排序
- 比较相邻的元素。 如果第一个比第二个大,就交换他们两个。
- 从开始第一对到结尾的最后一对,每对相邻元素都作同样的工作,。 这步做完后,最后的元素会是最大的数。
- 针对所有的元素重复以上的步骤,除了最后一个。
- 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
1 | public void sort(int[] arr){ |
选择排序
首先在序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。
1 | public void sort(int[] arr){ |
基数排序
将所有待比较数值(正整数)统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。
1 | public void radix_sort(int[] array){ |
总结
Algorithm | Average | Best | Worst | extra space | stable |
---|---|---|---|---|---|
冒泡排序 | O(N^2) | O(N) | O(N^2) | O(1) | 稳定 |
直接插入排序 | O(N^2) | O(N) | O(N^2) | O(1) | 稳定 |
希尔排序 | O(N^1.3) | O(N) | O(N^2) | O(1) | 不稳定 |
简单选择排序 | O(N^2) | O(N^2) | O(N^2) | O(1) | 不稳定 |
快速排序 | O(NlogN) | O(NlogN) | O(N^2) | O(logN)~O(N^2) | 不稳定 |
归并排序 | O(NlogN) | O(NlogN) | O(NlogN) | O(N) | 稳定 |
堆排序 | O(NlogN) | O(NlogN) | O(NlogN) | O(1) | 不稳定 |
计数排序 | O(d*(N+K)) | O(d*(N+K)) | O(d*(N+K)) | O(N+K) | 稳定 |
排序方法的选择
- 若n较小(如n≤50),可采用直接插入或直接选择排序。
当记录规模较小时,直接插入排序较好;否则因为直接选择移动的记录数少于直接插人,应选直接选择排序为宜。
- 若文件初始状态基本有序(指正序),则应选用直接插人、冒泡或随机的快速排序为宜;
- 若n较大,则应采用时间复杂度为O(nlgn)的排序方法:快速排序、堆排序或归并排序。
快速排序是目前基于比较的内部排序中被认为是最好的方法,当待排序的关键字是随机分布时,快速排序的平均时间最短;
堆排序所需的辅助空间少于快速排序,并且不会出现快速排序可能出现的最坏情况。这两种排序都是不稳定的;
若要求排序稳定,则可选用归并排序。
但本章介绍的从单个记录起进行两两归并的排序算法并不值得提倡,通常可以将它和直接插入排序结合在一起使用。先利用直接插入排序求得较长的有序子文件,然后再两两归并之。因为直接插入排序是稳定 的,所以改进后的归并排序仍是稳定的。
参考资料: