文章目录
- 1. 算法的性能
- 排序算法稳定性:
- 2. 算法的实现
- 2.1. 冒泡排序
- 算法步骤:
- 2.2. 选择排序
- 算法步骤:
- 2.3. 直接插入排序
- 算法步骤:
- 2.4. 堆排序
- 算法步骤:
- 2.5. 两路合并排序
- 算法步骤:
- 2.6. 快速排序
- 算法步骤:
- 辅助方法 swap:
1. 算法的性能
排序算法稳定性:
假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,则称这种排序算法是稳定的.即在原序列中,array[i]==array[j],且 i<j,而在排序后的序列中,array[i] 仍在 array[j]之前。
算法 | 平均时间复杂度 | 最好时间 | 最坏时间 | 空间复杂度 | 稳定性 |
冒泡 | O(n ^2) | O(n) | O(n ^2) | O(1) | 稳定 |
简单选择 | O(n ^2) | O(n) | O(n ^2) | O(1) | 不稳定 |
直接插入 | O(n ^2) | O(n) | O(n ^2) | O(1) | 稳定 |
堆排序 | O(nlog n) | O(nlog n) | O(nlog n) | O(1) | 不稳定 |
两路合并 | O(nlog n) | O(nlog n) | O(nlog n) | O(n) | 稳定 |
快排 | O(nlog n) | O(nlog n) | O(n ^2) | O(nlog n) ~ O(∞) | 不稳定 |
来源 :《算法设计与分析》- 陈慧南版 P.81 表 5-2
2. 算法的实现
2.1. 冒泡排序
算法步骤:
- 比较相邻的元素。如果第一个比第二个大,就交换位置。
- 对每一组相邻元素作同样的操作,从开始第一组到结尾的最后一组。
- 针对所有的元素重复以上的步骤,每一趟最大元素冒到需排序元素的尾部,故比较范围在减小。
- 对每趟都在减少的需排序元素重复上面的步骤,直到没有任何一对数字需要比较。
冒泡排序最好情况原数组有序,时间为 O(n),最大的元素冒到数组末尾。
public void bubbleSort(int a[]) {
int length = a.length;
for (int i = 0; i < length - 1; i++) { // 共有 n 个元素,也就需要比较 n-1 趟
for (int j = 0; j < length - 1 - i; j++) {//数组尾部存放的元素已经有序,比较范围在缩小
if (a[j] > a[j + 1]) {
swap(a, j, j+1);
}
}
}
}
2.2. 选择排序
算法步骤:
- 在未排序序列中找到最小元素,存放到排序序列的起始位置.
- 从剩余未排序元素中继续寻找最小元素,然后放到已排序序列的末尾。
- 重复以上步骤,直到所有元素均排序完毕。
选择排序的主要优点与数据移动有关。如果某个元素位于正确的最终位置上,则它不会被移动.
public void selectSort(int a[]) {
for (int i = 0; i < a.length - 1; i++) {
int minIndex = i;
for (int j = i + 1; j < a.length; j++) {
if (a[j] < a[i]) {
minIndex = j; // 每次选择未排序序列中最小值
}
}
if (minIndex != i) {
swap(a, minIndex, i); // 放到数组头部已排序部分的末尾
}
}
}
2.3. 直接插入排序
算法步骤:
- 构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到应插入位置。
- 反复把已排序元素逐步向后挪位,为最新元素提供插入空间, 并插入正确位置.
- 重复以上步骤,直至元素遍历完毕.
public void insertSort(int a[]) {
for (int i = 0;i <a.length;i++) {
int get = a[i]; // 默认 a[0]为排序好的元素
int j = i-1;
while (j >= 0 && a[j] >get) {// a[i] 需要往有序序列插入,序列中大于 get 的元素需往后移动,挪出位置
a[j+1]= a[j];
j--;
}
a[j+1] = get; // 插入正确位置
}
}
2.4. 堆排序
简单的公式描述一下堆的定义就是:
大顶堆:arr[i] >= arr[2i+1] && arr[i] >= arr[2i+2]
小顶堆:arr[i] <= arr[2i+1] && arr[i] <= arr[2i+2]
算法步骤:
- 将无序序列构建成一个堆,根据升序降序需求选择大顶堆或小顶堆;
- 将堆顶元素与末尾元素交换,将最大元素"沉"到数组末端;
- 重新调整结构,使其满足堆定义,然后继续交换堆顶元素与当前末尾元素,反复执行调整+交换步骤,直到整个序列有序。
// 堆调整方法
public void heapify(int a[], int i, int size) {//从a[i]向下调整堆
int leftChild = 2 * i + 1;
int rightChild = 2 * i + 2;
int max = i;
//找父节点和两个子节点中的最大值,确保父节点为最大值
if (leftChild < size && a[leftChild] > a[max])
max = leftChild;
if (rightChild < size && a[rightChild] > a[max])
max = rightChild;
if (max != i) {
swap(a, i, max);
heapify(a, max, size);//记录交换前子节点位置,从该位置向下调整堆
}
}
//建最大堆方法,堆顶为数组中最大元素
public int buildHeap(int a[], int n) {
int heapSize = n;
for (int i = heapSize / 2 - 1; i >= 0; i--) {//从非叶子结点开始调整,直到a[0]根
heapify(a, i, heapSize);
}
return heapSize;
}
//堆排序方法
public void heapSort(int a[], int n) {
int heapSize = buildHeap(a, n);
while (heapSize > 1) {
// 将堆顶元素与堆的最后一个元素互换,并将最后一个元素从以后的堆调整中忽略--heap_size
swap(a, 0, --heapSize);
heapify(a, 0, heapSize);//从新的堆顶调整堆
}
}
2.5. 两路合并排序
算法步骤:
- 将有n个元素的序列不断划分为n个长度为1的有序子序列.
- 两两合并子序列,直到得到一个长度为n的有序序列时结束。
public void mergeSort(int a[], int left, int right) {
if (left == right) return;
int mid = (left + right) / 2; // 1. 不断将数组分划为两路子序列
mergeSort(a, left, mid);
mergeSort(a, mid + 1, right);
merge(a, left, mid, right); // 2.归并数组
}
//按照规则归并数组
public void merge(int a[], int left, int mid, int right) {
int len = right - left + 1;
int temp[] = new int[len];
int index = 0;
int i = left, j = mid + 1;
while (i <= mid && j <= right) {
temp[index++] = a[i] <= a[j] ? a[i++] : a[j++];
}//左右两部分长度不一样时的处理
while (i <= mid) {
temp[index++] = a[i++];
}
while (j <= right) {
temp[index++] = a[j++];
}
for (int k = 0; k < len; k++) {
a[left++] = temp[k];//把排序好的数组赋值给原数组
}
}
2.6. 快速排序
算法步骤:
- 选定数组最左边的元素为比较基点 target.
- 双指针思想,左指针从左往右查找比 target 大的元素下标 i , 右指针从右往左查找比 target 小的元素下标 j, 如果 i < j 成立说明一趟遍历未结束, 交换两元素位置.
- 一趟遍历完毕,交换当前比较基点 target 和 右指针下标.当前数组右指针下标元素变为target,以其分界,左边都是比 target 小的元素, 右边都是比target 大的元素, 将数组划分为两部分,重复以上步骤.
//快排
public void quickSort(int a[], int left, int right) {
int i, j;
while (left < right) {
i = left;
j = right + 1;
do {
while (a[++i] < a[left]) ;
while (a[--j] > a[left]) ;
if (i < j) swap(a, i, j);//从左往右找比标记元素大,从右往左找比标记元素小,交换
} while (i < j);
swap(a, left, j);//一趟完毕,交换标记元素位置
quickSort(a, left, j - 1);
quickSort(a, j + 1, right);
return;
}
}
辅助方法 swap:
private void swap(int a[], int i, int j) {
int temp = a[i];
a[i] = a[j];
a[j] = temp;
}