文章目录
- 1.前置知识
- 1.1稳定性
- 1.2内部排序和外部排序
- 1.3是不是比较的排序
- 2.直接插入排序
- 2.1思想
- 2.2实现
- 2.3时间复杂度和空间复杂度、稳定性
- 3.希尔排序
- 3.1思想
- 3.2实现
- 3.3时间复杂度和空间复杂度、稳定性
- 4.选择排序
- 4.1思想
- 4.2实现
- 4.3时间复杂度和空间复杂度、稳定性
- 5.堆排序
- 5.1思想
- 5.2实现
- 5.3时间复杂度和空间复杂度、稳定性
- 6.冒泡排序
- 6.1思想
- 6.2实现
- 6.3时间复杂度和空间复杂度、稳定性
- 7.快速排序
- 7.1思想
- 7.2实现
- 7.3非递归实现
- 7.4时间复杂度和空间复杂度、稳定性
- 8.归并排序
- 8.1思想
- 8.2实现
- 8.3非递归实现
- 8.4时间复杂度和空间复杂度、稳定性
- 9.海量数据的排序问题
- 9.1内部排序和外部排序
- 9.2举例
- 10.排序算法复杂度及其稳定性总结
- 11.基于非比较的排序
- 12.排序的一些经典习题
- 1.快速排序算法是基于(A)的一个排序算法。
- 2.对数据54,38,96,23,15,72,60,45,83进行从小到大的直接插入排序时,当把第8个记录45插入到有序表时,为找到插入位置需比较(C)次?(采用从后往前比较)
- 3.以下排序方式中占用O(n)辅助存储空间的是(D)
- 4.下列排序算法中稳定且时间复杂度为O(n^2)的是(B)
- 5.关于排序,下面说法不正确的是(D)
- 6.下列排序法中,最坏情况下时间复杂度最小的是(A)
- 7.设一组初始记录关键字序列为(65,56,72,99,86,25,34,66),则以第一个关键字65为基准而得到的一趟快速排序结果是(A)
1.前置知识
1.1稳定性
(1)稳定的排序:排序前后两个相等的数据的相对位置不发生改变
(2)不稳定的排序:排序前后两个相等的数据的相对位置发生改变
注意:一个本身稳定的排序一定也可以实现为不稳定的,但是一个本身就不稳定的排序无法实现为稳定的
1.2内部排序和外部排序
(1)内部排序:把数据全部加载到内存当中进行排序
(2)外部排序:数据太多,内存不能存储了,放在磁盘,优盘等地方进行排序
1.3是不是比较的排序
(1)基于比较的排序:关键字之间进行大小比较
(2)非比较的排序:不比较大小
2.直接插入排序
适用于数量不大且基本有序的数据排序
2.1思想
把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列
2.2实现
将i下标的值放到tmp中,j 下标等于 i-1下标,若j下标的值大于tmp,j+1下标的值就等于j下标的值;否则,j+1下标的值就等于tmp
public static void insertSort(int[] array){
for (int i = 0; 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 {
array[j+1] = tmp;
break;
}
}
//这里是for循环进不去了(此时j = -1),就把j+1下标放上tmp
array[j+1] = tmp;
}
}
2.3时间复杂度和空间复杂度、稳定性
(1)时间复杂度:
最坏情况:O(N^2)(倒序的数据)
最好情况:O(N)(有序的情况下)
(2)空间复杂度:O(1)
(3)稳定性:稳定
3.希尔排序
也叫缩小增量排序,是对直接插入排序的优化
3.1思想
先选定一个整数,把待排序文件中所有数据分成个组,所有距离相等的在同一组内,并对每一组内的记录进行排序。然后,重复上述分组和排序的工作。当到达1时,所有记录在统一组内排好序
3.2实现
按照gap = n/2,gap = gap/2,进行分组后进行直接插入排序,直到gap = 1在进行一次直接插入排序
public static void shellSort(int[] array){
int gap = array.length;
while (gap > 1){
gap /= 2;
shell(array,gap);
}
shell(array,1);
}
public static 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-=gap) {
if (array[j] > tmp){
array[j+gap] = array[j];
}else {
array[j+gap] = tmp;
break;
}
}
array[j+gap] = tmp;
}
}
3.3时间复杂度和空间复杂度、稳定性
(1)时间复杂度:和函数本身有关,大概在O(N^1.3)~ O(N^1.5)之间
(2)复杂度:O(1)
(3)稳定性:不稳定
4.选择排序
4.1思想
每一次从待排序的数据元素中选出最小的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完
4.2实现
每一次从待排序的数据元素中选出最小的一个元素,放在第一个排序的位置
public static void selectSort(int[] array){
for (int i = 0; i < array.length; i++) {
for (int j = i+1; j < array.length; j++) {
if (array[i] > array[j]){
int tmp = array[i];
array[i] = array[j];
array[j] = tmp;
}
}
}
}
优化后:其实时间复杂度并不会减少,只是交换的次数变少了
public static void selectSort(int[] array){
for (int i = 0; i < array.length; i++) {
int minIndex = i;
for (int j = i+1; j < array.length; j++) {
if (array[minIndex] > array[j]){
minIndex = j;
}
}
int tmp = array[minIndex];
array[minIndex] = array[i];
array[i] = tmp;
}
}
4.3时间复杂度和空间复杂度、稳定性
(1)时间复杂度:O(N^2)
(2)复杂度:O(1)
(3)稳定性:不稳定
5.堆排序
5.1思想
排升序建立一个大堆,向下调整进行排序,每次交换最后一个值和根结点,在进行调整成大根堆,这样最后一个值就是最大的并且有序,依次进行直到根结点结束
5.2实现
public static void heapSort(int[] array){
//创建一个大堆
createHeap(array);
//向下调整进行排序,每次交换最后一个值和根结点,在进行调整成大根堆,这样最后一个值就是最大的并且有序
int end = array.length-1;
while (end > 0){
int tmp = array[0];
array[0] = array[end];
array[end] = tmp;
shiftDown(array,0,end);
end--;
}
}
//创建一个大根堆
public static void createHeap(int[] array){
for (int parent = (array.length-1-1)/2 ;parent >= 0;parent--){
shiftDown(array,parent,array.length);
}
}
//向下调整进行排序
public static void shiftDown(int[] array,int parent,int len){
int child = parent*2+1;
while (child < len){
//判断有没有右孩子,如果右孩子大于左孩子,让child++,找到左右孩子的最大值指向child
if (child + 1<len&&array[child+1]>array[child]){
child++;
}
//如果孩子的值大于父亲的值,交换孩子的值和父亲的值
if (array[child] > array[parent]){
int tmp = array[child];
array[child] = array[parent];
array[parent] = tmp;
parent = child;
child = parent*2+1;
}else {
break;
}
}
}
5.3时间复杂度和空间复杂度、稳定性
(1)时间复杂度:O(NlogN),和数据是否有序无关
(2)复杂度:O(1)
(3)稳定性:不稳定
6.冒泡排序
6.1思想
将键值较大的记录向序列的尾部移动,键值较小的记录向序列的前部移动,左>右就交换
6.2实现
public static void bubbleSort(int[] array){
//比较的趟数
for (int i = 0; i < array.length-1; i++) {
for (int j = 0; j < array.length-1; j++) {
//左>右,就交换左右的值
if (array[j] > array[j+1]){
int tmp = array[j];
array[j] = array[j+1];
array[j+1] = tmp;
}
}
}
}
优化:
public static void bubbleSort(int[] array){
boolean flg = false;
//比较的趟数
for (int i = 0; i < array.length-1; i++) {
for (int j = 0; j < array.length-1-i; j++) {
//左>右,就交换左右的值
if (array[j] > array[j+1]){
int tmp = array[j];
array[j] = array[j+1];
array[j+1] = tmp;
//说明这一趟发生了交换,就需要继续比较
flg = true;
}
}
//说明这一趟没有发生交换,就不需要继续比较,数组已经有序了
if (flg == false){
break;
}
}
}
6.3时间复杂度和空间复杂度、稳定性
(1)时间复杂度:
最坏情况:O(N^2)
最好情况:O(N)(优化之后且数组有序)
(2)复杂度:O(1)
(3)稳定性:稳定
7.快速排序
7.1思想
任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止
7.2实现
public static void quickSort(int[] array){
quick(array,0,array.length-1);
}
private static void quick(int[] array,int start,int end){
if (start >= end){
return;
}
//当数据量小于一定值的时候可以使用其他排序达到优化的目的
if (end - start + 1 <= 100){
//使用直接插入排序进行优化
insertSort2(array,start,end);
return;
}
//找到三个数中排序中间的数字(对快速排序的一个优化,优化之前,当数据量很大的时候,会出现异常)
int index = threeIndex(array,start,end);
//将中间数字放到起始位置
swap(array,index,start);
int pivot = partition(array,start,end);
//递归排序pivot左边的数据
quick(array,start,pivot-1);
//递归排序pivot右边的数据
quick(array,pivot+1,end);
}
//当待排序的数据达到一定数量的时候,使用直接插入排序进行优化
public static void insertSort2(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 {
array[j + 1] = tmp;
break;
}
}
array[j + 1] = tmp;
}
}
//三数取中法(优化),保证左右均匀划分
private static int threeIndex(int[] array,int left,int right){
int mid = (left+right)/2;
if (array[left] < array[right]){
if (array[mid] > array[left]){
return left;
}else if (array[right] > array[mid]){
return right;
}else {
return mid;
}
}else {
//array[left] > array[right]
if (array[mid] > array[right]){
return right;
}else if (array[left]> array[mid]){
return left;
}else {
return mid;
}
}
}
//一次划分函数(挖坑法)
private static int partition(int[] array,int left,int right){
int tmp = array[left];
while (left < right){
//这里的等号不能省略,否则会死循环
while (array[right] >= tmp&&right>left){
right--;
}
//右边找到小于tmp 的元素
array[left] = array[right];
while (array[left] <= tmp&&left<right){
left++;
}
//左边找到大于tmp 的元素
array[right] = array[left];
}
array[left] = tmp;
return left;
}
7.3非递归实现
public static void quickSort1(int[] array){
Stack<Integer> stack = new Stack<>();
int start = 0;
int end = array.length-1;
//进行一次划分找到第一个基准
int pivot = partition(array,start,end);
//基准的右边至少有两个元素,就让右边的起始和终止下标入栈
if (pivot < end - 1){
stack.push(pivot + 1);
stack.push(end);
}
//基准的左边至少有两个元素,就让右边的起始和终止下标入栈
if (pivot > start + 1){
stack.push(start);
stack.push(pivot - 1);
}
while (!stack.empty()){
end = stack.pop();
start = stack.pop();
pivot = partition(array,start,end);
if (pivot < end - 1){
stack.push(pivot + 1);
stack.push(end);
}
if (pivot > start + 1){
stack.push(start);
stack.push(pivot - 1);
}
}
}
//一次划分函数,找基准
private static int partition(int[] array,int left,int right){
int tmp = array[left];
while (left < right){
while (array[right] >= tmp&&right>left){
right--;
}
//右边找到小于tmp 的元素
array[left] = array[right];
while (array[left] <= tmp&&left<right){
left++;
}
//左边找到大于tmp 的元素
array[right] = array[left];
}
array[left] = tmp;
return left;
}
7.4时间复杂度和空间复杂度、稳定性
(1)时间复杂度:
最好情况:O(NlogN)
最坏情况:O(N^2)
(2)复杂度:
最好情况:O(logN)
最坏情况:O(N)
(3)稳定性:不稳定
8.归并排序
8.1思想
采用分治法,将已有序的子序列合并,得到完全有序的序列,下图介绍的是二路归并
8.2实现
public static void mergeSort(int[] array){
mergeSortFunction(array,0,array.length-1);
}
public static void mergeSortFunction(int[] array,int low,int high){
int mid = (low + high) >>> 1;
if(low >= high){
return;
}
//归
mergeSortFunction(array,low,mid);
mergeSortFunction(array,mid+1,high);
//并
merge(array,low,high,mid);
}
//合并函数
public static void merge(int[] array,int start,int end,int mid){
int s1 = start;
// int e1 = mid;
int s2 = mid + 1;
// int e2 = end;
int[] tmp = new int[end - start + 1];
int k = 0;
while (s1 <= mid && s2 <= end){
if (array[s1] > array[s2]){
tmp[k++] = array[s2++];
}else {
tmp[k++] = array[s1++];
}
}
while (s1 <= mid){
tmp[k++] = array[s1++];
}
while (s2 <= end){
tmp[k++] = array[s2++];
}
for (int i = 0; i < tmp.length; i++) {
array[i + start] = tmp[i];
}
}
8.3非递归实现
public static void mergeSort2(int[] array){
int gap = 1;//gap是元素个数
while (gap <array.length) {
for (int i = 0; i <array.length; i+=gap*2) {
int low = i;
int mid = low+gap-1;
if (mid >= array.length){
mid = array.length-1;
}
int high = mid+gap;
if (high >= array.length){
high = array.length-1;
}
merge(array,low,high,mid);
}
gap = gap*2;
}
}
//合并函数
public static void merge(int[] array,int start,int end,int mid){
int s1 = start;
// int e1 = mid;
int s2 = mid + 1;
// int e2 = end;
int[] tmp = new int[end - start + 1];
int k = 0;
while (s1 <= mid && s2 <= end){
if (array[s1] > array[s2]){
tmp[k++] = array[s2++];
}else {
tmp[k++] = array[s1++];
}
}
while (s1 <= mid){
tmp[k++] = array[s1++];
}
while (s2 <= end){
tmp[k++] = array[s2++];
}
for (int i = 0; i < tmp.length; i++) {
array[i + start] = tmp[i];
}
}
8.4时间复杂度和空间复杂度、稳定性
(1)时间复杂度:O(NlogN)
(2)复杂度:O(N)
(3)稳定性:稳定
9.海量数据的排序问题
9.1内部排序和外部排序
(1)内部排序:把数据全部加载到内存当中进行排序
(2)外部排序:排序过程需要在磁盘等外部存储进行的排序
9.2举例
内存只有 1G,需要排序的数据有 100G,怎么解决???
解决:因为内存中因为无法把所有数据全部放下,所以需要外部排序,使用归并排序解决。先把文件切分成 200 份,每个 512 M;分别对 512 M 排序,因为内存已经可以放的下,所以任意排序方式都可以; 进行 2 路归并,同时对 200 份有序文件做归并过程,最终结果就有序了
10.排序算法复杂度及其稳定性总结
11.基于非比较的排序
(1)计数排序: 统计相同元素出现次数;根据统计的结果将序列回收到原来的序列中(稳定)
(2)基数排序:将整数按位数切割成不同的数字,然后按每个位数分别比较
(2)桶排序:每个桶存储一定范围的元素,通过映射函数,将待排序数组中的元素映射到各个对应的桶中,对每个桶中的元素进行排序,最后将非空桶中的元素逐个放入原序列中
12.排序的一些经典习题
1.快速排序算法是基于(A)的一个排序算法。
A:分治法
B:贪心法
C:递归法
D:动态规划法
2.对数据54,38,96,23,15,72,60,45,83进行从小到大的直接插入排序时,当把第8个记录45插入到有序表时,为找到插入位置需比较©次?(采用从后往前比较)
A: 3
B: 4
C: 5
D: 6
思路:当进行45排序的时候,他前面的数据一定都有序了,此时的数据顺序为:15,23,38,54,60,72,96,83,因此45需要和96,72,60,54,38比较,一共需要比较4次
3.以下排序方式中占用O(n)辅助存储空间的是(D)
A: 简单排序
B: 快速排序
C: 堆排序
D: 归并排序
思路:没有简单排序这个说法;快速排序的空间复杂度最坏情况是N,最好情况是logN;堆排序的空间复杂度为1;归并排序的空间复杂度为N
4.下列排序算法中稳定且时间复杂度为O(n^2)的是(B)
A: 快速排序
B: 冒泡排序
C: 直接选择排序
D: 归并排序
思路:快速排序不稳定,时间复杂度为N和NlogN;冒泡排序的时间复杂度为 N^2;直接选择排序不稳定,时间复杂度为 N^2;归并排序的时间复杂度为NlogN
5.关于排序,下面说法不正确的是(D)
A: 快排时间复杂度为O(N*logN),空间复杂度为O(logN)
B: 归并排序是一种稳定的排序,堆排序和快排均不稳定
C:序列基本有序时,快排退化成冒泡排序,直接插入排序最快
D: 归并排序空间复杂度为O(N), 堆排序空间复杂度的为O(logN)
思路:堆排序空间复杂度的为O(1)
6.下列排序法中,最坏情况下时间复杂度最小的是(A)
A: 堆排序
B: 快速排序
C: 希尔排序
D: 冒泡排序
思路:堆排序:NlogN
快速排序 :N^2
希尔排序:N^1.5
冒泡排序:N^2
7.设一组初始记录关键字序列为(65,56,72,99,86,25,34,66),则以第一个关键字65为基准而得到的一趟快速排序结果是(A)
A: 34,56,25,65,86,99,72,66
B: 25,34,56,65,99,86,72,66
C:34,56,25,65,66,99,86,72
D: 34,56,25,65,99,86,72,66
思路:从后面找比65小的数字,让他与65所在的位置交换,第一次交换的结果为34,56,72,99,86,25,65,66;然后从前面找到比65大的数组,让他与65所在的位置交换,第二次交换的结果为34,56,65,99,86,25,72,66;依次类推,第三次交换的结果为34,56,25,65,86,99,72,66