快速排序/归并排序/插入排序
快速排序 快速排序使用分治法(Divide and conquer)策略来把一个串行(list)分为两个子串行(sub-lists)。
从数列中挑出一个元素,称为 “基准”(pivot);
重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;partition函数返回pivot的位置。
递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序;(递归改非递归——栈)
1 2 3 4 5 6 public void QuickSort (int [] array,int left,int right) { if (left >= right) return ; int index = Partition(array,left,right); QuickSort(array,left,index - 1 ); QuickSort(array,index + 1 ,right); }
Partition的几种写法
1 2 3 4 5 6 7 8 9 10 11 public int Partition (int [] array,int left,int right) { int pivot = array[right]; while (left < right){ while (left < right && array[left] <= pivot){ left++;} while (left < right && array[right] >= pivot){ right--;} swap(array[left],array[right]); } swap(array[left],pivot); return left; }
1 2 3 4 5 6 7 8 9 10 11 12 public int Partition (int [] nums, int left, int right) { int pivot = nums[left]; while (left < right){ while (left < right && nums[right] >= pivot){ right--;} nums[left] = nums[right]; while (left < right && nums[left] <= pivot){left++;} nums[right] = nums[left]; } nums[left] = pivot; return left; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 public int Partition (int [] nums, int left, int right) { int pivot = nums[right]; int i = left - 1 ; for (int j = left; j <= right - 1 ; j++) { if (nums[j] <= pivot) { i = i + 1 ; swap(nums[i], nums[j]); } } swap(nums[i + 1 ], nums[right]); return i + 1 ; } public int Partition (int [] array,int left,int right) { if (left < right){ int pivot = array[right]; int cur = left; int pre = cur - 1 ; while (cur < right){ while (array[cur] < pivot && pre++ != cur){ swap(array[cur],array[pre]);} cur++; } swap(array[pre++],array[right]); return pre; } return -1 ; }
交换元素
1 2 3 4 5 private void swap (int a, int b) { int temp = a; a = b; b = temp; }
1.基准的选择 (1)固定基准 。如果数组元素是随机的,划分过程不产生极端情况,那么程序的运行时间不会有太大的波动。 如果数组元素已经基本有序时,此时的划分就容易产生最坏的情况,即快速排序变成冒泡排序,时间复杂度为O(n^2)。 (2)随机基准 。虽然使用随机基准能解决待排数组基本有序的情况,但是由于这种随机性的存在,对其他情况的数组也会有影响(若数组元素是随机的,使用固定基准常常优于随机基准)。 (3)三数取中 。选取数组开头,中间和结尾的元素,通过比较,选择中间的值作为快排的基准。其实可以将这个数字扩展到更大(例如5数取中,7数取中等)。这种方式能很好的解决待排数组基本有序的情况,而且选取的基准没有随机性。
2.快速排序分析 快速排序是一种不稳定的排序算法。
最坏时间复杂度: 即元素都分到一个子序列,另一个子序列为空的情况,时间复杂度为O(N2)。
最好时间复杂度: 即序列是均分为两个子序列,时间复杂度是O(NlogN) ,与归并排序差不多。
平均时间复杂度:O(NlogN) 。
空间复杂度:O(logN)
3.快速排序的优化 优化1:序列长度达到一定大小时,使用插入排序
当快排达到一定深度后,划分的区间很小时,再使用快排的效率不高。当待排序列的长度达到一定数值后,可以使用插入排序。由《数据结构与算法分析》(Mark Allen Weiness所著)可知,当待排序列长度为5~20之间,此时使用插入排序能避免一些有害的退化情形。
1 2 3 4 5 6 7 8 9 10 public void QuickSort (int [] array,int left,int right) { if (right - left + 1 < 10 ){ InsertSort(arr,low,high); return ; } if (left >= right) return ; int index = Partition(array,left,right); QuickSort(array,left,index - 1 ); QuickSort(array,index + 1 ,right); }
归并排序 采用经典的分治 (divide-and-conquer)策略(分治法将问题分 (divide)成一些小的问题然后递归求解,而治(conquer) 的阶段则将分的阶段得到的各答案”修补”在一起,即分而治之)。
将数列划分为两部分;
递归地分别对两个子序列进行归并排序;
合并两个子序列。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public static void sort (int [] arr, int L, int R) { if (L == R) return ; int mid = L + ((R - L) >> 1 ); sort(arr, L, mid); sort(arr, mid + 1 , R); merge(arr, L, mid, R); } public static void merge (int [] arr, int L, int mid, int R) { int [] temp = new int [R - L + 1 ]; int i = 0 ; int p1 = L; int p2 = mid + 1 ; while (p1 <= mid && p2 <= R) { temp[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];} while (p1 <= mid) { temp[i++] = arr[p1++];} while (p2 <= R) { temp[i++] = arr[p2++];} for (i = 0 ; i < temp.length; i++) { arr[L + i] = temp[i];} }
复杂度分析 时间复杂度:O(nlogn) 空间复杂度:O(N) ,归并排序需要一个与原数组相同长度的数组做辅助来排序 稳定性:归并排序是稳定的排序算法,temp[i++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++];
这行代码可以保证当左右两部分的值相等的时候,先复制左边的值,这样可以保证值相等的时候两个元素的相对位置不变。
插入排序 插入排序与打扑克时整理手上的牌非常类似。摸来的第1张牌无须整理,此后每次从桌上的牌(无序区)中摸最上面的1张并插入左手的牌(有序区)中正确的位置上。为了找到这个正确的位置,须自左向右(或自右向左)将摸来的牌与左手中已有的牌逐一比较。
1 2 3 4 5 6 7 8 9 10 11 12 public void insertionsort (int arr[]) { int n = arr.length; for (int i=1 ; i<n; i++){ int key = arr[i]; int j = i-1 ; while (j>=0 && arr[j] > key) { arr[j+1 ] = arr[j]; j = j-1 ; } arr[j+1 ] = key; } }
复杂度分析 不稳定排序算法。 最坏情况:$O(n^2)$ 最好情况:$O(n) $ 平均情况:$O(n^2)$