排序
- 插入(顺序+二分)、希尔、选择(普通+双向选择)、堆、冒泡、快速(递归+非递归、partition(hoare+挖坑法+前后遍历法)、合并(递归+非递归)
- 升序为例
插入排序
选择无序区间的第一个元素插入到有序区间的合适位置
插入+二分查找:将顺序查找改进为二分查找,同时注意排序的稳定性
//插入排序
public static void insertSort(int[] nums) {
for (int i = 1; i < nums.length; i++) {
int v = nums[i];
int j = i-1;
//在 [0,i)找到应该插入的位置
// j >= 0保证边界情况
// v < nums[j]保证排序稳定性
for (; j >= 0 && v < nums[j]; j--) {
nums[j+1] = nums[j]; //元素后移
}
nums[j+1] = v;
}
}
//改进 插入+二分查找
public static void bsInsertSort(int[] nums) {
for (int i = 0; i < nums.length; i++) {
int v = nums[i];
int left = 0;
int right = i;
//二分查找 常规版 不具有稳定性
// while (left <= right) {
// int mid = (left + right) / 2;
// if (v < nums[mid]) {
// right = mid - 1;
// } else if (v > nums[mid]){
// left = mid + 1;
// } else {
// break;
// }
// }
//具有稳定性
while (left < right) {
int mid = (left + right) / 2;
if (v >= nums[mid]) {
left = mid + 1;
} else {
right = mid;
}
}
for (int j = i; j > left; j--) {
nums[j] = nums[j-1];
}
nums[left] = v;
}
}
希尔排序
将距离为gap的数据分为同一组,然后对每一组的数据进行排序,gap不断减小,重新分组和排序。当gap=1时,即为插入排序
//希尔排序
public static void shellSort(int[] nums) {
for (int gap = nums.length; gap > 1; gap /= 2) {
shellSortHelper(nums, gap);
}
shellSortHelper(nums, 1);
}
private static void shellSortHelper(int[] nums, int gap) {
for (int i = 0; i < nums.length; i++) {
int v = nums[i];
int j = i - gap;
for (; j >= 0 && v < nums[j]; j -= gap) {
nums[j+gap] = nums[j];
}
nums[j+gap] = v;
}
}
选择排序
从无序区间里选出一个最小的元素,放在有序区间的后面
双向选择排序:在一个无序区间里,同时找到最大值和最小值,将最小值放在最前面,最大值放在最后面
//选择排序
public static void selectSort(int[] nums) {
for (int i = 0; i < nums.length; i++) {
int minIndex = i;
for (int j = i; j < nums.length; j++) {
if (nums[minIndex] > nums[j]) {
minIndex = j;
}
}
swap(i, minIndex, nums);
}
}
//双向选择排序
public static void opSelectSort(int[] nums) {
int low = 0;
int high = nums.length - 1;
//[low, high]表示一个无序区间
//在这个区间里找到最大值和最小值
while (low <= high) {
int min = low;
int max = low;
for (int i = low + 1; i <= high; i++) {
if (nums[min] > nums[i]) {
min = i;
}
if (nums[max] < nums[i]) {
max = i;
}
}
swap(min, low, nums); //将最小值移到最前面
//如果最大值的位置刚好是最前面的位置,那么上一步的交换之后,最大值的位置就在最小值上
if (max == low) {
max = min;
}
swap(max, high, nums); //将最大值移到最后面
low++;
high--;
}
}
堆排序
通过堆来找到无序区间的最大值。排升序建大堆,排降序建小堆
//堆排序
public static void heapSort(int[] nums) {
createHeap(nums);
for (int i = 0; i < nums.length; i++) {
swap(0, nums.length-1-i, nums);
shiftDown(nums.length-1-i, 0, nums);
}
}
//建堆
private static void createHeap(int[] nums) {
for (int i = (nums.length-1-1)/2; i >= 0; i--) { //从倒数第一个非叶子节点开始调整
shiftDown(nums.length, i, nums);
}
}
//向下调整(大根堆)
private static void shiftDown(int size, int index, int[] nums) {
int parent = index;
int child = 2*parent+1;
while (child < size) {
//找到左右子树最大的那个
if (child + 1 < size && nums[child] < nums[child+1]) {
child = child+1;
}
//结点与子树比较,若小于子树则交换,否则退出循环,不必向下调整
if (nums[parent] < nums[child]) {
swap(parent, child, nums);
} else {
break;
}
parent = child;
child = 2*parent+1;
}
}
冒泡排序
在无序区间,通过相邻数的比较,把较大的数冒泡到区间的最后,直至数组整体有序
//冒泡排序
public static void bubbleSort(int[] nums) {
for (int i = 0; i < nums.length-1; i++) { //要比较多少趟
boolean isSorted = true;
for (int j = 0; j < nums.length-i-1; j++) { //在一趟中,相邻比较
if (nums[j] > nums[j+1]) {
swap(j, j+1, nums);
isSorted = false;
}
}
//如果这一趟中没有比较则说明数组已经有序
if (isSorted) {
break;
}
}
}
快速排序
1.partition:选择一个基准值,从后往前找比基准值小的数,从前往后找比基准值大的数,两数交换,如循环,使得整个区间里,比基准值小的数放在其左边,比基准值大的数放在其右边
2.采用分治思想,对左右两个小区间按照同样方式处理
ps:如果基准值选择最左边的数,则先从后往前找,最后重复的元素一定比基准值小 ;如果基准值选择最右边的数,则先从前往后找,最后重复的元素一定比基准值大
//快速排序
public static void quickSort(int[] nums) {
quickSortHelper(0, nums.length-1, nums);
}
private static void quickSortHelper(int left, int right, int[] nums) {
if (left >= right) {
return;
}
int pivotIndex = partition(left, right, nums);
quickSortHelper(left, pivotIndex-1, nums);
quickSortHelper(pivotIndex+1, right, nums);
}
//一次快排
private static int partition(int left, int right, int[] nums) {
int pivot = nums[left];
while (left < right) {
while (left < right && nums[right] > pivot) {
right--;
}
while (left < right && nums[left] < pivot) {
left++;
}
swap(left, right, nums);
}
nums[left] = pivot;
return left;
}
//非递归版本
private static void quickSort2(int[] nums) {
Stack<Integer> stack = new Stack<>();
stack.push(nums.length - 1);
stack.push(0);
while (!stack.isEmpty()) {
int left = stack.pop();
int right = stack.pop();
if (left >= right) {
continue;
}
int pivotIndex = partition(left, right, nums);
stack.push(right);
stack.push(pivotIndex+1);
stack.push(pivotIndex-1);
stack.push(left);
}
}
partition的其他实现方式
//挖坑法 不进行交换,而是赋值
private static int partition2(int left, int right, int[] nums) {
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;
}
//前后遍历法
//定义基准值为nums[right],定义两个指针,初始时,front指向起始位置的前一个,rear指向起始位置
//rear向后走,找到比基准值小的数,交换rear与++front,直至rear走到right结束
//最后交换++front与right
private static int partition3(int left, int right, int[] nums) {
int front = left-1; //指大
int pivot = nums[right];
for (int rear = left; rear < right; rear++) { //rear找小
if (nums[rear] < pivot) {
swap(rear, ++front, nums);
}
}
swap(++front, right, nums);
return front;
}
合并排序
先使子序列有序,再使子序列段间有序,最后把两个有序的子数组合并成一个有序的数组
//合并排序
public static void mergeSort(int[] nums) {
mergeSortHelper(0, nums.length, nums); //没有-1
}
private static void mergeSortHelper(int low, int high, int[] nums) {
if (low >= high || high - low == 1) { //区间为空区间或只有一个元素,不用排序
return;
}
int mid = (low+high)/2;
//递归对子区间归并排序
mergeSortHelper(low, mid, nums);//[low, mid)
mergeSortHelper(mid, high, nums);//[mid, high)
merge(low, mid, high, nums);
}
//合并两个有序数组
private static void merge(int low, int mid, int high, int[] nums) {
int i = low;
int j = mid;
int t = high - low;
int[] extra = new int[t];
int k = 0;
while (i < mid && j < high) {
//保证稳定性
if (nums[i] <= nums[j]) {
extra[k++] = nums[i++];
} else {
extra[k++] = nums[j++];
}
}
while (i < mid) {
extra[k++] = nums[i++];
}
while (j < high) {
extra[k++] = nums[j++];
}
for (int x = 0; x < t; x++) {
nums[low+x] = extra[x];
}
}
//非递归版本
public static void mergeSort2(int[] nums) {
for (int i = 1; i < nums.length; i *= 2) {
for (int j = 0; j < nums.length; j += 2*i) {
int low = j;
int mid = j+i;
if (mid >= nums.length) {
continue;
}
int high = mid + i;
if (high > nums.length) {
high = nums.length;
}
merge(low, mid, high, nums);
}
}
}
总结
时间复杂度 最好/平均/最差 | 空间复杂度 | |||
---|---|---|---|---|
插入排序- | O(n) | O(n^2) | O(n^2) | O(1) |
希尔排序 | O(n) | O(n^1.3) | O(n^2) | O(1) |
选择排序 | O(n^2) | O(1) | ||
堆排序 | O(n*log(n)) | O(1) | ||
冒泡排序- | O(n) | O(n^2) | O(n^2) | O(1) |
快速排序 | O(n*log(n)) | O(n*log(n)) | O(n^2) | O(log(n))/O(n) |
合并排序- | O(n*log(n)) | O(n) |