【数据结构】 排序算法
一、排序
1.1 排序是什么?
1.2 排序的应用
1.3 常见排序算法
二、常见排序算法的实现
2.1 插入排序
2.1.1 直接插入排序
(1)图解
(2)直接插入排序 解题思路
(3)Java 代码实现
public class Sort {
/**
* 直接插入排序
* 时间复杂度: 最坏情况 O(N^2) 最好情况O(N) -- 数据有序的情况下
* 直接插入排序使用场景: 给定的数据 趋于有序时,可以使用直接插入排序。
* 稳定性: 稳定
* @param array
*/
public static void insertSort(int[] array){
for (int i = 1; i < array.length; i++) {//从第2个元素开始排序
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;
}
}
//j=-1,把tmp的值再放回来
array[j+1] = tmp;
}
}
}
测试类:
import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.Random;
public class Test {
//测试 直接排序法的时间效率
//构造 有序数组
public static void order(int[] array) {
for (int i = 0; i < array.length; i++) {
array[i] = i;
}
}
//构造 逆序数组
public static void reverseOrder(int[] array){
for (int i = 0; i < array.length; i++) {
array[i] = array.length -i;
}
}
//构造 随机数组
public static void randomOrder(int[] array){
Random random = new Random();
for (int i = 0; i < array.length; i++) {
array[i] = random.nextInt(array.length);
}
}
public static void testInsertSort(int[] array){
array = Arrays.copyOf(array,array.length);
long startTime = System.currentTimeMillis();
Sort.insertSort(array);
long endTime = System.currentTimeMillis();
System.out.println("直接插入排序耗时:"+(endTime-startTime));
}
public static void testShellSort(int[] array){
array = Arrays.copyOf(array,array.length);
long startTime = System.currentTimeMillis();
Sort.insertSort(array);
long endTime = System.currentTimeMillis();
System.out.println("假设 希尔排序耗时:"+(endTime-startTime));
}
public static void main(String[] args) {
int[] array = new int[1_0000];
reverseOrder(array);
testInsertSort(array);
testShellSort(array);
}
public static void main1(String[] args) {
int[] array = {81,12,31,4,15,6};
Sort.insertSort(array);
System.out.println(Arrays.toString(array));
//[4, 6, 12, 15, 31, 81]
}
}
2.1.2 希尔排序
(1)希尔排序法的基本思想:
先选定⼀个整数,把待排序⽂件中所有记录分成多个组,所有距离为的记录分在同⼀组内,并对每⼀组内的记录进⾏排序。然后,取,重复上述分组和排序的⼯作。当到达=1时,所有记录在统⼀组内排好序。
(2)图解
(3)Java 代码实现
/**
* 希尔排序
* 时间复杂度:O(n^1.3)
* 空间复杂度:O(1)
* 稳定性:不稳定
* @param array
*/
public static void shellSort(int[] array){
int gap = array.length / 2;
while(gap > 0){
shell(array,gap);
gap /= 2;
}
shell(array,1);
}
public static void shell(int[] array,int gap){
for (int i = gap; i < array.length; i++) {//从第2个元素开始排序
int tmp = array[i];
int j = i-gap;
for (; j >= 0 ; j -= gap) {
if(array[j] > tmp){
array[j+gap] = array[j];
}else{
break;
}
}
array[j+gap] = tmp;
}
}
2.2 选择排序
2.2.1 直接选择排序
2.2.1.1 方法1
(1)图解
(2)直接排序解题思路
(3)Java 代码实现
/**
* 直接选择排序
* 时间复杂度:O(N^2)
* 空间复杂度:O(1)
* 稳定性:不稳定
*/
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;
}
}
swap(array,minIndex,i);
}
}
private static void swap(int[] array,int i,int j){
int tmp = array[i];
array[i] = array[j];
array[j] = tmp;
}
测试类:
public static void main(String[] args) {
int[] array = {81,12,31,4,15,6};
Sort.selectSort(array);
System.out.println(Arrays.toString(array));
//[4, 6, 12, 15, 31, 81]
}
2.2.1.1 方法2
(1)图解
(2)Java 代码
//选择排序 方法2
/**
* 每次找到2个数据 一个最大 一个最小
*/
public static void selectSort2(int[] array){
int left = 0;
int right = array.length-1;
while(left < right){
int minIndex = left;
int maxIndex = left;
for (int i = left+1; i <= right ; i++) {
if(array[i] < array[minIndex]){
minIndex = i;
}
if(array[i] > array[maxIndex]){
maxIndex = i;
}
}
swap(array,minIndex,left);
//假设第一个值 是最大值 81 3 6 2 4
if(left == maxIndex){
maxIndex = minIndex;
}
swap(array,maxIndex,right);
left++;
right--;
}
}
}
2.2.2 堆排序(数组形式)
(1)图解
(2)Java 代码
//堆排序
/**
* 时间复杂度:O(n*log2n)
* 空间复杂度:O(1)
* 稳定性:不稳定
*/
public static void heapSort(int[] array){
createHeap(array);
int end = array.length -1;
while(end > 0){
swap(array,0,end);
siftDown(array,0,end);
end--;
}
}
public static void createHeap(int[] array){
for (int parent = (array.length-1-1)/2; parent >= 0 ; parent--) {
siftDown(array,parent,array.length);
}
}
public static void siftDown(int[] array,int parent,int length){
int child = 2 * parent + 1;
while(child < length){
if(child+1 < length && array[child] < array[child+1]){//存在右孩子 并且 左孩子比右孩子小
child++;
}
if(array[child] > array[parent]){
swap(array,child,parent);
parent = child;
child = 2 * parent + 1;
}else{
break;
}
}
}
2.3 交换排序
2.3.1 冒泡排序
//冒泡排序
/**当前代码是优化过的版本,分析时间复杂度的时候,不考虑优化情况
* 时间复杂度:O(n^2); 如果考虑优化,最好情况下时间复杂度为O(n)
* 空间复杂度:O(1)
* 稳定性:稳定
*/
public static void bubbleSort(int[] array){
//趟数:10个数据 比较9趟
for (int i = 0; i < array.length-1; i++) {
boolean flg = false;//未优化的版本:没有flg
//每一趟的比较次数
for (int j = 0; j < array.length-1-i; j++) {
//未优化的版本:j < array.length-1
if(array[j] > array[j+1]){
swap(array,j,j+1);
flg = true;
}
}
if(!flg){
break;
}
}
}
2.3.2 快速排序
2.3.2.1 Hoare版 快速排序
(1)图解
(2)Java 代码实现
//Hoare版 快速排序
/**时间复杂度:O((n^log2n)
* 当前代码的最坏情况下:O(N^2) 有序 或 逆序
*空间复杂度: O(log2n) 最好
* O(N) 最坏
* 稳定性:不稳定
*/
public static void quickSort(int[] array){
quick(array,0,array.length-1);
}
public static void quick(int[] array,int start,int end){
if(start >= end){
return;
}
int par = partition(array,start,end);
quick(array,start,par-1);
quick(array,par+1,end);
}
private static int partitionHoare(int[] array,int left,int right){
int i = left;
int tmp = array[left];
while(left < right){
while(left < right && array[right] >= tmp){
right--;
}
while(left < right && array[left] <= tmp){
left++;
}
swap(array,left,right);
}
swap(array,left,i);
return left;
}
2.3.2.2 挖坑法 快速排序
private static int partition(int[] array,int left,int right){
int tmp = array[left];
while(left < right){
while(left < right && array[right] >= tmp){
right--;
}
array[left] = array[right];
while(left < right && array[left] <= tmp){
left++;
}
array[right] = array[left];
}
array[left] = tmp;
return left;
}
2.3.3 快排的优化
- 三数取中
三数取中优化:
//三数取中
int index = midSum(array,start,end);
swap(array,start,index);
int par = partition2(array,start,end);
quick(array,start,par-1);
quick(array,par+1,end);
}
public static int midSum(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[mid] > array[right]){
return right;
}else{
return mid;
}
}else{
if(array[mid] > array[left]){
return left;
}else if(array[mid] < array[right]){
return right;
}else{
return mid;
}
}
}
快速排序:
//Hoare版 快速排序
/**时间复杂度:O((n^log2n)
* 当前代码的最坏情况下:O(N^2) 有序 或 逆序
*空间复杂度: O(log2n) 最好
* O(N) 最坏
* 稳定性:不稳定
*/
public static void quickSort(int[] array){
quick(array,0,array.length-1);
}
public static void quick(int[] array,int start,int end){
if(start >= end){
return;
}
//三数取中
int index = midSum(array,start,end);
swap(array,start,index);
int par = partition2(array,start,end);
quick(array,start,par-1);
quick(array,par+1,end);
}
public static int midSum(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[mid] > array[right]){
return right;
}else{
return mid;
}
}else{
if(array[mid] > array[left]){
return left;
}else if(array[mid] < array[right]){
return right;
}else{
return mid;
}
}
}
private static int partitionHoare(int[] array,int left,int right){
int i = left;
int tmp = array[left];
while(left < right){
while(left < right && array[right] >= tmp){
right--;
}
while(left < right && array[left] <= tmp){
left++;
}
swap(array,left,right);
}
swap(array,left,i);
return left;
}
//挖坑法 快速排序
private static int partition2(int[] array,int left,int right){
int tmp = array[left];
while(left < right){
while(left < right && array[right] >= tmp){
right--;
}
array[left] = array[right];
while(left < right && array[left] <= tmp){
left++;
}
array[right] = array[left];
}
array[left] = tmp;
return left;
}
- 递归到小的子区间时,可以考虑使用插入排序
直接插入排序:
public static void quick(int[] array,int start,int end){
if(start >= end){
return;
}
if(end-start+1 <= 10){
//采用直接插入排序,把当前区间排序
insertSortRange(array,start,end);
return;
}
//三数取中
int index = midSum(array,start,end);
swap(array,start,index);
int par = partition2(array,start,end);
quick(array,start,par-1);
quick(array,par+1,end);
}
public static void insertSortRange(int[] array,int left,int right){
for (int i = left+1; i <= right; i++) {
int tmp = array[i];
int j = i-1;
for (; j >= left ; j--) {
if(array[j] > tmp){
array[j+1] = array[j];
}else{
break;
}
}
array[j+1] = tmp;
}
}
快速排序:
//Hoare版 快速排序
/**时间复杂度:O((n^log2n)
* 当前代码的最坏情况下:O(N^2) 有序 或 逆序
*空间复杂度: O(log2n) 最好
* O(N) 最坏
* 稳定性:不稳定
*/
public static void quickSort(int[] array){
quick(array,0,array.length-1);
}
public static void quick(int[] array,int start,int end){
if(start >= end){
return;
}
if(end-start+1 <= 10){
//采用直接插入排序,把当前区间排序
insertSortRange(array,start,end);
return;
}
//三数取中
int index = midSum(array,start,end);
swap(array,start,index);
int par = partition2(array,start,end);
quick(array,start,par-1);
quick(array,par+1,end);
}
public static void insertSortRange(int[] array,int left,int right){
for (int i = left+1; i <= right; i++) {
int tmp = array[i];
int j = i-1;
for (; j >= left ; j--) {
if(array[j] > tmp){
array[j+1] = array[j];
}else{
break;
}
}
array[j+1] = tmp;
}
}
public static int midSum(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[mid] > array[right]){
return right;
}else{
return mid;
}
}else{
if(array[mid] > array[left]){
return left;
}else if(array[mid] < array[right]){
return right;
}else{
return mid;
}
}
}
private static int partitionHoare(int[] array,int left,int right){
int i = left;
int tmp = array[left];
while(left < right){
while(left < right && array[right] >= tmp){
right--;
}
while(left < right && array[left] <= tmp){
left++;
}
swap(array,left,right);
}
swap(array,left,i);
return left;
}
//挖坑法 快速排序
private static int partition2(int[] array,int left,int right){
int tmp = array[left];
while(left < right){
while(left < right && array[right] >= tmp){
right--;
}
array[left] = array[right];
while(left < right && array[left] <= tmp){
left++;
}
array[right] = array[left];
}
array[left] = tmp;
return left;
}
- 前后指针法 快速排序
//前后指针法 快速排序
public static int partition(int[] array,int left,int right){
int prev = left;
int cur = left+1;
while(cur <= right){
if(array[cur] <= array[right] && array[++prev] != array[cur]){
swap(array,cur,prev);
}
cur++;
}
swap(array,prev,left);
return prev;
2.3.4 快速排序非递归
(1)图解
(2)Java 代码实现
//非递归的快速排序
/** 时间复杂度:O(N*logN)
* 空间复杂度:O(logN)
* 不稳定
*/
public static void quickSortNor(int[] array){
int left = 0;
int right = array.length -1;
int par = partition2(array,left,right);
Stack<Integer> stack = new Stack<>();
//左边有2个元素及以上
if(par > left+1){
stack.push(left);
stack.push(par-1);
}
//右边有2个元素及以上
if(par < right-1){
stack.push(par+1);
stack.push(right);
}//相当于把0 4 6 9扔进里面了
while(!stack.isEmpty()){
right = stack.pop();
left = stack.pop();
par = partition2(array,left,right);
if(par > left+1){
stack.push(left);
stack.push(par-1);
}
//右边有2个元素及以上
if(par < right-1){
stack.push(par+1);
stack.push(right);
}
}
}
2.4 归并排序
(1)图解
(2)Java 代码实现
//归并排序
/** 时间复杂度:O(N*log2N)
* 空间复杂度:O(N)
* 稳定
*/
public static void mergeSort(int[] array){
mergeSortChild(array,0,array.length-1);
}
private static void mergeSortChild(int[] array,int left,int right){
if( left>= right){
return;
}
int mid = (left+right) / 2;
mergeSortChild(array, left, mid);
mergeSortChild(array, mid + 1, right);
//开始合并
merge(array, left, mid, right);
}
private static void merge(int[] array, int left, int mid, int right) {
//临时数组
int[] tmpArr = new int[right-left+1];
int k = 0;//tmpArr数组下标
int s1 = left;
int e1 = mid;
int s2 = mid+1;
int e2 = right;
//当2个段 都有数据的时候
while(s1 <= e1 && s2 <= e2){
if(array[s1] < array[s2]){
tmpArr[k++] = array[s1++];
}else{
tmpArr[k++] = array[s2++];
}
}
//一个段走完了 把另一个段的数据 拷贝到临时数组
while(s1 <= e1){
tmpArr[k++] = array[s1++];
}
while(s2 <= e2){
tmpArr[k++] = array[s2++];
}
//临时数组当中存储的是有序的数组 --> 拷贝数据到原始数组当中
for (int i = 0; i < k; i++) {
array[i+left] = tmpArr[i];
}
}
(3)非递归 归并排序
//非递归 归并排序
public static void mergeSortNor(int[] array) {
if (array == null || array.length <= 1) {
return;
}
int gap = 1; // 初始子数组长度
while (gap < array.length) {
// 遍历整个数组,每次合并两个相邻的子数组
for (int i = 0; i < array.length; i += 2 * gap) {
int left = i;
int mid = Math.min(i + gap - 1, array.length - 1); // 防止越界
int right = Math.min(i + 2 * gap - 1, array.length - 1); // 防止越界
// 合并 [left, mid] 和 [mid+1, right]
merge(array, left, mid, right);
}
gap *= 2; // 子数组长度翻倍
}
}
三、各排序算法的复杂度及稳定性
四、 其他排序
4.1 计数排序
(1)图解
(2)java 代码实现
//计数排序
/** 使用场景:数组集中在某个范围内
* 时间复杂度:O(MAX(N,范围))
* 空间复杂度:O(范围)
* 稳定
*/
public static void countSort(int[] array){
//1.求最大值 和 最小值
int max = array[0];
int min = array[0];
for (int i = 1; i < array.length; i++) {
if(array[i] < min){
min = array[i];
}
if(array[i] > max){
max = array[i];
}
}
//2.定义计数数组,并求数组长度
int len = max - min + 1;
int[] countArray = new int[len];
//3.遍历原始数组,计数数组开始计数
for (int i = 0; i < array.length; i++) {
int index = array[i] - min;
countArray[index]++;
}
//4.遍历计数数组
int k = 0;//array数组的新下标
for (int i = 0; i < countArray.length; i++) {
while(countArray[i] != 0){
array[k] = i+min;
k++;
countArray[i]--;
}
}
//执行到这里 array数组全部都有序了
}