常见的排序算法:

按照插入排序分:1.直接插入排序 2.希尔排序

按照选择排序分:1.选择排序 2.堆排序

按照交换排序分:1.冒泡排序 2.快速排序

按照归并排序分:1.归并排序

常见的排序思想:是使用双指针或者三指针,在同一个数组上遍历、比较、交换。

1、直接插入排序

类比于扑克牌抽排,双指针思路。

1.时间复杂度:最坏情况下O(N^2),如54321,最好情况下O(N),如12345(只用i动,j不用走)

2.空间复杂度:O(1)

3.稳定性:稳定

4.使用场景:元素集合越接近有序,直接插入排序算法时间效率越高。

public void insertSort(int[] array){

//[27,15,9,18,28]

for (int i = 1; i < array.length; i++) {

int temp = array[i];

int j = i-1;

for (; j >=0 ; j--) {

if (array[j] > temp){

array[j+1] = array[j];

}else {

//如果不用调整,则表示已经找到了该放的位置了

//因为前面都有序了

break;

}

}

//当j循环走完以后,要么就是走完了所有,要么就是找到了最小位置

//所以走完以后直接把j+1的位置赋值为temp即可

array[j+1] = temp;

}

}

2、希尔排序(缩小增量排序)

基本思想:先选定一个整数,把待排序文件中所有记录分成多个组,所有距离为的记录分在同一组内,并对每一组内的记录进行排序。当到达 =1时,所有记录在统一组内排好序。

时间复杂度:O(n^1.25)~O(1.6*n^1.25)

空间复杂度:O(1)

稳定性:不稳定

希尔排序的特性总结:

1. 希尔排序是对直接插入排序的优化。

2. 当gap > 1时都是预排序,目的是让数组更接近于有序。当gap == 1时,数组已经接近有序的了,这样就会很快。这样整体而言,可以达到优化的效果。我们实现后可以进行性能测试的对比。

3. 希尔排序的时间复杂度不好计算,因为gap的取值方法很多,导致很难去计算,因此在好些树中给出的希尔排序的时间复杂度都不固定。

跳着分的目的是让交换之后,大的数都在后面,小的数都在前面。前面的这些趟都是预排序,最后1趟才是真正的排序。每一组都是插入排序,越有序越快。

public void shellSort(int[] array){

int gap = array.length;

//gap表示array数组,分为多少个组

while (gap > 1){

//每次缩短一半的分组

gap /= 2;

shell(array,gap);

}

}

/**

* 对每组进行插入排序

* @param array

* @param gap

*/

public void shell(int[] array,int gap) {

for (int i = gap; i < array.length ; i++) {

int tmp = array[i];

int j = i-gap;

for (; j >=0 ; j-=gap) {

if (array[j] > tmp){

array[j+gap] = array[j];

}else {

break;

}

}

array[j+gap] = tmp;

}

}

3、选择排序

每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完。

时间复杂度:O(n^2)

空间复杂度:O(1)

稳定性:不稳定

public void selectSort(int[] array){

//双指针思想

for (int i = 0; i < array.length; i++) {

int miniIndex = i;

for (int j = i+1; j < array.length; j++) {

//如果j指向的数值小于最小值,那么就更新最小值索引

if (array[j] <= array[miniIndex]){

miniIndex = j;

}

}

//找到最小值索引以后,就跟i位置交换

swap(array,i,miniIndex);

}

}

//3指针思想

public void doubleSelectSort(int[] array){

int left = 0;

int right = array.length-1;

while (left < right){

int minIndex = left;

int maxIndex = left;

//走一趟left和right之间的值

for (int i = left + 1;i<= right;i++){

if (array[i] < array[minIndex]){

minIndex = i;

}

if (array[i] > array[maxIndex]){

maxIndex = i;

}

}

//注意,如果最大值在第一个位置,会出现bug

//如果最大值在下标为0的位置,此时第一个swap会把最大值换到minIndex位置上

//而第二个swap会把0位置的最小值换到数组最后一个位置,就乱了

swap(array,minIndex,left);

//如果最大值的索引,正好等于left

//交换完成以后,left存储的是最小值,minIndex存储的是最大值

//所以需要多加一步,将minIndex赋给maxIndex

if (maxIndex == left){

maxIndex = minIndex;

}

swap(array,maxIndex,right);

left++;

right--;

}

}

4、堆排序

利用堆的特性来排序。排升序是用大根堆,排降序是用小根堆。其实就是不断地将堆中的树进行向上升序或者向下排序,由于堆中的元素是大根堆或者小根堆排列的,所以最终的树就是有序的了。

时间复杂度:O(N*logN)

空间复杂度:O(1)

稳定性:不稳定

public void heapSort(int[] array){

//先把数组创建成堆

createHeap(array);

//从最后一棵树开始排序

int end = array.length-1;

while (end > 0){

swap(array,0,end);

siftDown(array,0,end);

end--;

}

}

private void siftDown(int[] array,int parent, int lenght) {

int child = 2 * parent + 1;

while (child < lenght){

if (child + 1

child++;

}

if (array[child] > array[parent]){

swap(array,child,parent);

parent = child;

child = 2*parent+1;

}else {

break;

}

}

}

private void createHeap(int[] array) {

//从最后一个父亲结点开始创建

for (int parent = (array.length-1-1)/2; parent >=0 ; parent--) {

siftDown(array,parent,array.length);

}

}

5、冒泡排序

双指针

时间复杂度:O(n^2),优化以后最好情况下可以达到O(n)

空间复杂度:O(1)

稳定性:稳定

public void bubbleSort(int[] array){

for (int i = 0; i < array.length-1; i++) {

boolean flg = false;

for (int j = 0; j < array.length-1-i; j++) {

if (array[i] > array[j]){

swap(array,i,j);

flg = true;

}

}

if (!flg){

break;

}

}

}

6、快速排序

任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。

时间复杂度:最好的情况下,O(N*logN),当最坏的情况下,O(N^2),逆序或者有序

空间复杂度:O(logN)

稳定性:不稳定

上述为快速排序递归实现的主框架,发现与二叉树前序遍历规则非常像,同学们在写递归框架时可想想二叉树前序遍历规则即可快速写出来,后序只需分析如何按照基准值来对区间中数据进行划分的方式即可。

其实最核心的任务就是找到相交点,交换。

6.1 Hoare法

递归以后层数多了可能会栈溢出。因为怕就怕他切分的这棵树,左边长右边短,或者左边短右边长。如果是均匀切分的话,其实是效果比较好的。

private void quick(int[] array,int start,int end){

if (start >= end){

return;

}

//每次相遇的位置

int pivot = partitionHoare(array,start,end);

//以相遇的位置作为分割点,切分以后递归继续找

quick(array,start,pivot-1);

quick(array,pivot+1,end);

}

private int partitionHoare(int[] array, int left, int right) {

//待比较元素

int tmp = array[left];

int i =left;

while (left < right){

//单独的循环,不能一直减到超过左边的边界

while (left < right && array[right] >= tmp){

right--;

}

while (left < right && array[left] <= tmp){

left ++;

}

//交换left和right找到的值

swap(array,left,right);

}

//此时left和right都在相遇点了,将相遇点换到最前面

swap(array,i,left);

return left;

}

6.2 挖坑法(Hoare衍生出来的)

挖坑法就是把基准从数组中拿出来,数组中就留下了一个坑,然后同样从右边和左边遍历,遇到大于或者小于基准的就把那个元素,拿到坑这个位置放下,反复循环,知道左右索引相遇,就把基准填入最后一个坑里。

private int partitionHole(int[] array,int left,int right){

//待比较元素

int tmp = array[left];

while (left < right){

//单独的循环,不能一直减到超过左边的边界

while (left < right && array[right] >= tmp){

right--;

}

//right找到比基准小的值,就把right所指向的元素填入left位置

//因为此时left位置就是坑

array[left] = array[right];

while (left < right && array[left] <= tmp){

left ++;

}

array[right] = array[left];

}

array[left] = tmp;

return left;

}

值得注意的2个细节:

1,array[right] >= tmp 为什么要加等于?

如果不加等于,会死循环。因为left=right,left无法++right无法--,会不停地交换这两个值。

2,为什么从右边开始而不是先从左边开始?

如果先让left先走,那么走到相遇地方的时候,left指向的是一个比较大的数,此时将left和最左边交换,会让这个大的数换到最左边去。而如果是从右边先开始的话,right不断--,当遇到left的时候,right走到的是一个较小的数,此时再与最左边交换,就没问题。

6.3 前后指针法

cur用来指向比基准小的数,prev用来推着比基准大的数,cur不停地找比基准小的数,去和prev指向的比基准大的数交换。给人的感觉就是prev在不停地推着这些比基准大的数往前滚动,cur作为开路先锋去找小的数来交换。

//前后指针法1

private int partitionBFPoint1(int[] array, int left, int right) {

int prev = left ;

int cur = left+1;

while (cur <= right) {

if(array[cur] < array[left] && array[++prev] != array[cur]) {

swap(array,cur,prev);

}

cur++;

}

swap(array,prev,left);

return prev;

}

//前后指针法2

private int partitionBFPoint2(int[] array, int left, int right) {

int d = left + 1;

int pivot = array[left];

for (int i = left + 1; i <= right; i++) {

if (array[i] < pivot) {

swap(array, i, d);

d++;

}

}

swap(array, d - 1, left);

return d - 1;

}

6.4 快速排序小结

三种快速排序的方法:

1.Hoare法

2.挖坑法

3.前后指针法

这三种方法每次划分以后的序列顺序,有可能是不一样的。

当面临一个序列,要你判断他用的哪一种快速排序的方法的时候。根据以往的经验,先后检验的顺序是挖坑法、Hoare法、前后指针法。