目录
一.排序相关概念
- 排序:使一串记录按照某个关键字的大小,递增或递减的排列起来
- 稳定性:在未排序的序列中,存在
多个
具有相同关键字
的记录,如果在经过排序之后,这些记录的相对次序
保持不变
,则称这种排序算法是稳定的
- 内部排序:数据元素全部放在内存中的排序
- 外部排序:相关元素太多不能同时放在内存中,根据排序过程的要求不能在内外存之间移动数据的排序
二.常见排序算法
1.堆排序
- 基本思想:建立大/小根堆,将堆尾元素(end位置元素)与堆顶元素(堆中最大/小值)交换,调整end位置前的所有结点使其重新满足大/小根堆的性质,同时end位置向前移动,循环这个过程直至end位置移动堆顶位置(
每次都将堆的最大/小值移动到堆尾
)
- 时间复杂度:
O(NlogN)
- 空间复杂度:
O(1)
- 稳定性:不稳定
java
复制代码
public void heapSort(int[] array) {
createBigHeap(array);
int end = array.length - 1;
while (end > 0) {
swap(array, 0, end);
siftDown(array, 0, end);
end--;
}
}
public void createBigHeap(int[] array) {
for (int parent = (array.length - 1 - 1) / 2; parent >= 0; parent--) {
siftDown(array, parent, array.length);
}
}
public void siftDown(int[] array, int parent, int end) {
int child = 2 * parent + 1;
while (child < end) {
if (child + 1 < end && array[child] < array[child + 1]) {
child++;
}
if (array[child] > array[parent]) {
swap(array, child, parent);
parent = child;
child = 2 * parent + 1;
} else {
break;
}
}
}
public void swap(int[] array, int i, int j) {
int tmp = array[i];
array[i] = array[j];
array[j] = tmp;
}
2.插入排序
- 基本思想:把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列
- 时间复杂度:
O(N^2^)
- 空间复杂度:
O(1)
- 稳定性:稳定
- 特点:元素集合越接近
有序
,插入排序算法的时间效率
就越高
java
复制代码
public void insertSort(int[] array) {
for (int i = 1; i < array.length; i++) {
int tmp = array[i];// 获取插入的元素
int j = i - 1;
for (; j >= 0; j--) { // 寻找要插入的位置
if (array[j] > tmp) {
array[j + 1] = array[j];
} else {
break;// 找到了要插入的位置或者没找到(j移动到-1位置)
}
}
array[j + 1] = tmp;
}
}
3.希尔排序
- 基本思想:先选定一个整数gap,把待排序文件中所有记录
分成多个组
,所有距离为gap的记录分在同一组内,并对每一组内的记录进行排序。然后,重复上述分组和排序的工作。当gap=1时,所有记录排序完成
- 时间复杂度:
约O(N^1.25^)~N(1.6*N^1.25^)
- 空间复杂度:
O(1)
- 稳定性:不稳定
java
复制代码
public void shellSort(int[] array) {
int gap = array.length;
while (gap > 0) {
gap = gap / 2;
shell(array, 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--) {
if (array[j] > tmp) {
array[j + gap] = array[j];
} else {
break;
}
}
array[j + gap] = tmp;
}
}
4.选择排序
- 基本思想:每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完
- 时间复杂度:
O(N^2^)
- 空间复杂度:
O(1)
- 稳定性:不稳定
java
复制代码
public void selectSort(int[] array) {
for (int i = 0; i < array.length - 1; i++) {
int minIndex = i;
for (int j = i + 1; j < array.length; j++) {
if (array[j] < array[minIndex]) {
minIndex = j;
}
}
swap(array, i, minIndex);
}
}
public void swap(int[] array, int i, int j) {
int tmp = array[i];
array[i] = array[j];
array[j] = tmp;
}
5.冒泡排序
- 基本思想:在待排序的数据元素中,将相邻的两个数进行比较,若前面的数比后面的数大就交换两数,否则不交换;循环这个过程,直至最终完成排序(
小的数据向前移动,大的数据向后移动
)
- 时间复杂度:
O(N^2^)
- 空间复杂度:
O(1)
- 稳定性:稳定
java
复制代码
public void bubbleSort(int[] array) {
for (int i = 0; i < array.length - 1; i++) {
boolean flag = false;
for (int j = 0; j < array.length - 1 - i; j++) {
if (array[j] > array[j + 1]) {
swap(array, j, j + 1);
flag = true;
}
}
if (!flag) {
return;
}
}
}
6.快速排序
- 基本思想:任取待排序元素序列中的某元素作为
基准值
,按照该排序码将待排序集合分割
成两子序列,左子序列中所有元素均小于
基准值,右子序列中所有元素均大于
基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上
- 时间复杂度:
O(NlogN)
- 空间复杂度:
O(logN)
- 稳定性:不稳定
1.快速排序--递归(未优化)
java
复制代码
public void quickSort(int[] array) {
quick(array, 0, array.length - 1);
}
public void quick(int[] array, int start, int end) {
if (start >= end) {
return;
}
int pivot = partition1(array, start, end);// 基准值下标
// 左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值
quick(array, start, pivot - 1);// 递归左子序列
quick(array, pivot + 1, end);// 递归右子序列
}
// 将区间按照基准值划分为左右两半部分的常见方式:Hoare法和挖坑法
// Hoare法
public int partition1(int[] array, int left, int right) {
// left和right分别为array子序列的最左和最右元素下标
int tmp = array[left];// 基准值
int start = left;
while (left < right) {
// right向前走,寻找<=基准值的元素下标
while (left < right && array[right] >= tmp) {
right--;
}
// left向后走,寻找>=基准值的元素下标
while (left < right && array[left] <= tmp) {
left++;
}
// 交换left和right下标的元素
swap(array, left, right);
}
// left和right相遇,将基准值与相遇位置元素交换
swap(array, start, left);
return left;
}
// 挖坑法
public int partition2(int[] array, int left, int right) {
int tmp = array[left];
// left下标元素填入tmp中,留下一个坑(left下标)
while (left < right) {
// right向前走,寻找<=基准值的元素下标
while (left < right && array[right] >= tmp) {
right--;
}
array[left] = array[right];// 填入坑(left下标)中,留下一个坑(right下标)
// left向后走,寻找>=基准值的元素下标
while (left < right && array[left] <= tmp) {
left++;
}
array[right] = array[left];// 填入坑(right下标)中,留下一个坑(left下标)
}
// left和right相遇,将tmp填入相遇位置这个坑中
array[left] = tmp;
return left;
}
2.快速排序--递归(优化)
- 三数取中法选key(减少划分区间时没有左/右子序列的情况)
- 递归到小的区间时,使用
插入排序
java
复制代码
public void quickSort(int[] array) {
quick(array, 0, array.length - 1);
}
public void quick(int[] array, int start, int end) {
if (start >= end) {
return;
}
if (start - end + 1 <= 10) { // 子序列长度较短时,使用插入排序
insertSortSTE(array, start, end);
return;
}
int mid = middle(array, start, end);// 获取中间值的下标
swap(array, start, mid);// 交换start和mid下标元素,使得基准值为mid元素
int pivot = partition1(array, start, end);
quick(array, start, pivot - 1);
quick(array, pivot + 1, end);
}
public int middle(int[] array, int left, int right) { // 三数取中法(left,mid,right)
int mid = left + ((right - left) >> 1);
int[] num = {array[left], array[mid], array[right]};
Arrays.sort(num);
if (array[left] == num[1]) {
return left;
} else if (array[right] == num[1]) {
return right;
} else {
return mid;
}
}
// 指定区间插入排序
public void insertSortSTE(int[] array, int start, int end) {
for (int i = start + 1; i <= end; i++) {
int tmp = array[i];
int j = i - 1;
for (; j >= start; j--) {
if (array[j] > tmp) {
array[j + 1] = array[j];
} else {
break;
}
}
array[j + 1] = tmp;
}
}
3.快速排序--非递归
java
复制代码
public void quickSortNor(int[] array) {
int left = 0;
int right = array.length - 1;
int pivot = partition1(array, left, right);
Stack<Integer> stack = new Stack<>();
if (pivot - 1 > left) {
stack.push(left);
stack.push(pivot - 1);
}
if (pivot + 1 < right) {
stack.push(pivot + 1);
stack.push(right);
}
while (!stack.isEmpty()) {
right = stack.pop();
left = stack.pop();
pivot = partition1(array, left, right);
if (pivot - 1 > left) {
stack.push(left);
stack.push(pivot - 1);
}
if (pivot + 1 < right) {
stack.push(pivot + 1);
stack.push(right);
}
}
}
7.归并排序
- 基本思想:归并排序是建立在归并操作上的一种有效的排序算法,为分治法的一个典型应用,即将待排序的数据
分段排序
后合并
- 时间复杂度:O(NlogN)
- 空间复杂度:O(N)
- 稳定性:稳定
1.归并排序--递归
java
复制代码
public void mergeSort(int[] array) {
branch(array, 0, array.length - 1);
}
public void branch(int[] array, int left, int right) {
if (left >= right) {
return;
}
int mid = left + ((right - left) >> 1);
branch(array, left, mid);// 分解左子序列
branch(array, mid + 1, right);// 分解右子序列
merge(array, left, mid, right); // 合并子序列
}
public void merge(int[] array, int left, int mid, int right) {
int s1 = left;
int e1 = mid;
int s2 = mid + 1;
int e2 = right;
int index = 0;
int[] ans = new int[right - left + 1];
while (s1 <= e1 && s2 <= e2) {
if (array[s1] <= array[s2]) {
ans[index++] = array[s1++];
} else {
ans[index++] = array[s2++];
}
}
while (s1 <= e1) {
ans[index++] = array[s1++];
}
while (s2 <= e2) {
ans[index++] = array[s2++];
}
for (int i = left; i <= right; i++) {
array[i] = ans[i - left];
}
}
2.归并排序--非递归
java
复制代码
public void merge(int[] array, int left, int mid, int right) {
int s1 = left;
int e1 = mid;
int s2 = mid + 1;
int e2 = right;
int index = 0;
int[] ans = new int[right - left + 1];
while (s1 <= e1 && s2 <= e2) {
if (array[s1] <= array[s2]) {
ans[index++] = array[s1++];
} else {
ans[index++] = array[s2++];
}
}
while (s1 <= e1) {
ans[index++] = array[s1++];
}
while (s2 <= e2) {
ans[index++] = array[s2++];
}
for (int i = left; i <= right; i++) {
array[i] = ans[i - left];
}
}
public void mergeSortNor(int[] array) {
int gap = 1;
while (gap < array.length) {
for (int i = 0; i < array.length; i++) {
int left = i;
int mid = left + gap - 1;
if (mid >= array.length) {
mid = array.length - 1;
}
int right = mid + gap;
if (right >= array.length) {
right = array.length - 1;
}
merge(array, left, mid, right);
}
gap *= 2;
}
}