所谓排序就是使一串记录按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作
一、相关概念
稳定性:假设在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,则称这种排序算法是稳定的,否则称为不稳定的
内部排序:数据元素全部放在内存中的排序
外部排序:数据元素太多不能同时放在内存中,根据排序过程的要求不能在内外存之间移动数据的排序
二、常见的排序算法
插入排序:直接插入排序、希尔排序
选择排序:选择排序、堆排序
交换排序:冒泡排序、快速排序
归并排序:归并排序
1、插入排序
把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列
1.1直接插入排序
当插入第i个元素时,前面的前i-1个元素已经排好序,此时用第i个元素与第i-1个,第i- 2......元素进行比较,找到插入位置将第i个元素插入,原来位置上的元素顺序后移
public static void insertSort(int[] arr) {
for (int i = 1;i < arr.length;i++) {
int tmp = arr[i];
int j = i - 1;
while (j >= 0) {
if (arr[j] <= tmp) {
break;
}
arr[j + 1] = arr[j];
j--;
}
arr[j + 1] = tmp;
}
}
特性总结:
(1)元素集合越接近有序,直接插入排序算法的时间效率越高
(2)时间复杂度:O(N^2)
(3)空间复杂度:O(1)
(4)稳定性:稳定
1.2希尔排序(缩小增量排序)
先选定一个整数,把待排序文件中所有记录分成个组,所有距离相同的记录分为一个组内,并对每一组内的记录进行排序,然后重复上述的分组和排序工作,当达到=1时,所有记录在统一组内排好序
public static void shellSort(int[] arr) {
int gap = arr.length / 2;
while (gap > 0) {
shell(arr,gap);
gap /= 2;
}
}
private static void shell(int[] arr, int gap) {
for (int i = gap;i < arr.length;i++) {
int j = i - gap;
int tmp = arr[i];
while (j >= 0) {
if (tmp > arr[j]) {
break;
}
arr[j + gap] = arr[j];
j -= gap;
}
arr[j + gap] = tmp;
}
}
特性总结:
(1)希尔排序是对直接插入排序的优化
(2)当gap>1时都是预排序,目的是让数组更接近于有序,当gap==1时,数组已经接近有序的了,这样就会很快,这样整体而言,可以达到优化的效果
(3)稳定性:不稳定
(4)
2、选择排序
每一次从待排序的数据元素中选出最小的(或最大的)元素,存放在序列的起始位置,直到全部待排序的数据元素排完
2.1直接选择排序
在元素集合中选择关键码最大(小)的数据元素
若它不是这组元素中的最后一个(第一个)元素,则将它与这组元素中的最后一个(第一个)元素交换
在剩余的集合中,重复上述步骤,直到集合中剩余1个元素
public static void selectSort1(int[] arr) {
for (int i = 0;i < arr.length;i++) {
int minIndex = i;
for (int j = i + 1;j < arr.length;j++) {
if (arr[j] < arr[minIndex]) {
minIndex = j;
}
}
swap(arr,minIndex,i);
}
}
public static void selectSort2(int[] arr) {
int left = 0;
int right = arr.length - 1;
while (left < right) {
int minIndex = left;
int maxIndex = left;
for (int i = left + 1;i <= right;i++) {
if (arr[i] < arr[minIndex]) {
minIndex = i;
}
if (arr[i] > arr[maxIndex]) {
maxIndex = i;
}
}
if (left != minIndex) {
swap(arr,left,minIndex);
}
if (left == maxIndex) {
maxIndex = minIndex;
}
if (right != maxIndex) {
swap(arr,right,maxIndex);
}
left++;
right--;
}
}
private static void swap(int[] arr, int j, int i) {
int tmp = arr[j];
arr[j] = arr[i];
arr[i] = tmp;
}
特性总结:
(1)直接插入排序效率不是很好,实际上很少使用
(2)时间复杂度:O(N^2)
(3)空间复杂度:O(1)
(4)稳定性:不稳定
2.2堆排序
堆排序是利用堆的这种数据结构所设计的一种排序算法,它是选择排序的一种,它是通过堆来进行选择数据。注意:排升序要建大堆,降序要建小堆
public static void heapSort(int[] arr) {
createHeap(arr);
int end = arr.length - 1;
while (end >= 0) {
swap(arr,0,end);
shiftDowm(arr,0,end);
end--;
}
}
private static void createHeap(int[] arr) {
for (int parent = (arr.length - 2) / 2;parent >= 0;parent--) {
shiftDowm(arr,parent,arr.length);
}
}
private static void shiftDowm(int[] arr, int parent, int length) {
int child = parent * 2 + 1;
while (child < length) {
if ((child + 1) < length) {
if (arr[child] < arr[child + 1]) {
child++;
}
}
if (arr[parent] >= arr[child]) {
break;
}
swap(arr,parent,child);
parent = child;
child = parent * 2 + 1;
}
}
特性总结:
(1)堆排序使用堆来选数,效率就高了很多
(2)时间复杂度:O(N*logN)
(3)空间复杂度:O(1)
(4)稳定性:不稳定
3、交换排序
所谓交换就是根据序列中的两个记录键值的比较结果来对换这两个记录在序列中的位置,交换排序的特点是:将键值较大的记录向序列的尾部移动,键值较小的记录向序列的前部移动
3.1冒泡排序
public static void bubbleSort(int[] arr) {
boolean flag = false;
for (int i = 0;i < arr.length - 1;i++) {
for (int j = 0;j < arr.length - i - 1;j++) {
if (arr[j] > arr[j + 1]) {
swap(arr,j,j + 1);
flag = true;
}
}
//没有参与比较,说明数组已经有序
if (!flag) {
break;
}
}
}
特性总结:
(1)冒泡排序是一种非常容易理解的排序
(2)时间复杂度:O(N^2)
(3)空间复杂度:O(1)
(4)稳定性:稳定
3.2快速排序
在待排序的元素序列中的某元素作为基准,按照改排序码将该待排序列集合分割为两子序列,左序列中所有元素均小于该基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排序在相应位置上为止
private static void quickSortProcess(int[] arr, int left, int right) {
if (left > right) {
return;
}
int mid = findMid(arr,left,right);
quickSortProcess(arr,left,mid - 1);
quickSortProcess(arr,mid + 1,right);
}
将区间按照基准值划分为左右两半部分的常见方式有:
(1)Hoare版
//Hoare法找基准
private static int findMid(int[] arr, int left, int right) {
int midValue = arr[left];
int minIndex = left;
while (left < right) {
while (left < right && arr[right] >= midValue) {
right--;
}
while (left < right && arr[left] <= midValue) {
left++;
}
swap(arr,left,right);
}
swap(arr,left,minIndex);
return left;
}
(2)挖坑法
先将第一个数据存放在临时变量中,形成一个坑位
// 挖坑法找基准
private static int findMid1(int[] arr, int left, int right) {
int midValue = arr[left];
while (left < right) {
while (left < right && arr[right] >= midValue) {
right--;
}
arr[left] = arr[right];
while (left < right && arr[left] <= midValue) {
left++;
}
arr[right] = arr[left];
}
arr[left] = midValue;
return left;
}
特性总结:
(1)快速排序整体的综合性能和使用场景都是比较好的
(2)时间复杂度:O(N*logN)
(3)空间复杂度:O(logN)
(4)稳定性:不稳定
4、归并排序
该算法是采用分治算法的典型的应用,将已有序的子序列合并,得到完全有序的序列,即先使每个子序列有序,再使子序列段间有序,若两个有序表合并为一个有序表,称为二路合并
public static void mergeSort(int[] arr) {
mergeSortProcess(arr,0,arr.length - 1);
}
private static void mergeSortProcess(int[] arr, int left, int right) {
if (left >= right) {
return;
}
int mid = (left + right) / 2;
mergeSortProcess(arr,left,mid);
mergeSortProcess(arr,mid + 1,right);
//合并
merge(arr,left,mid,right);
}
private static void merge(int[] arr, int left, int mid, int right) {
int[] temp = new int[right - left + 1];
int s1 = left;
int e1 = mid;
int s2 = mid + 1;
int e2 = right;
int index = 0;
while (s1 <= e1 && s2 <= e2) {
if (arr[s1] < arr[s2]) {
temp[index++] = arr[s1++];
} else {
temp[index++] = arr[s2++];
}
}
while (s1 <= e1) {
temp[index++] = arr[s1++];
}
while (s2 <= e2) {
temp[index++] = arr[s2++];
}
for (int i = 0;i < temp.length;i++) {
arr[i + left] = temp[i];
}
}
特性总结:
(1)归并排序的缺点在于需要O(N)的空间的复杂度,归并排序的思考更多的是解决在磁盘中的外排序问题
(2)时间复杂度:O(N*logN)
(3)空间复杂度:O(N)
(4)稳定性:稳定
三、排序算法复杂度及稳定性分析