文章目录
- 一、插入排序
-
- [1.1 直接插入排序](#1.1 直接插入排序)
- [1.2 希尔排序](#1.2 希尔排序)
- 二、选择排序
-
- [2.1 直接选择排序](#2.1 直接选择排序)
- [2.2 堆排序](#2.2 堆排序)
- 三、交换排序
-
- [3.1 冒泡排序](#3.1 冒泡排序)
- [3.2 快速排序](#3.2 快速排序)
- 四、归并排序
-
- [4.1 归并排序](#4.1 归并排序)
- 五、有关各个排序的复杂度
- 总结
什么是稳定什么是不稳定
相对次序不变则稳定,相对次序改变则不稳定
一、插入排序
1.1 直接插入排序
核心就是找到i下标的数在前面的数组中应该放的位置,i在往后走的过程中,i前面的数组已经是拍好序的了
java
/**
* 排序方式:直接插入排序
* 时间复杂度:O(n^2),但当数据趋于有序时,比如有序数组,那么时间复杂度为O(n)
* 稳定性:稳定
* @param array
*/
public static 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 {
array[j + 1] = tmp;
break;
}
}
//走到这有两种可能,一种是已经将tmp放到正确的位置了
//一种是j到了-1的位置说明tmp是最小值,要将0位置的值改成tmp
array[j + 1] = tmp;
}
System.out.println("直接插入排序");
}
1.2 希尔排序
核心在于将数组分成一个一个的组,在组内进行直接插入排序,当gap越来越趋于1时,说明数组也在慢慢趋于有序
java
/**
* 排序方式:希尔排序
* 时间复杂度:根据不同的gap时间复杂度不同,可以粗略理解为O(n^1.25)
* 稳定性:不稳定
* @param array
*/
public static void shellSort(int[] array){
int gap = array.length;
while (gap > 1){
gap = gap/3 +1;
shellSortReal(array,gap);
}
System.out.println("希尔排序");
}
private static void shellSortReal(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;
}
}
二、选择排序
2.1 直接选择排序
核心要点就是从两边往中间逼近,在左右的范围中找到最大的数和最小的数,并将它们分别放在最右边和最左边
java
/**
* 排序方式:直接选择排序
* 时间复杂度:O(n^2)
* 稳定性:不稳定
* @param array
*/
public static void selectSort(int[] array){
int left = 0;
int right = array.length - 1;
while (left < right){
int minIndex = left;
int maxIndex = left;
for (int i = left; i <= right; i++) {
if(array[i] < array[minIndex]){
minIndex = i;
}
if(array[i] > array[maxIndex]){
maxIndex = i;
}
}
swap(array,minIndex,left);
if(maxIndex == left) {//预防出现最大值在left位置,然后left的最大值被换到了minIndex位置
maxIndex = minIndex;
}
swap(array,maxIndex,right);
left++;
right--;
}
System.out.println("直接选择排序");
}
private static void swap(int[] array, int i, int j){
int tmp = array[i];
array[i] = array[j];
array[j] = tmp;
}
2.2 堆排序
需要注意的是排升序要建大堆,排降序建小堆。
有关大小根堆的相关内容,我在《优先队列||堆》这篇文章中有介绍,有需要可以去看看,这里我们直接就用创建大根堆的代码不再解释了。
思路:将传入的数组组成大根堆,然后将堆顶元素和最后一个元素end所在指向的元素交换一下,这样数组最后一个元素就是最大的,再将换后的数组重新按堆排序,同时end--,找到倒数第二大的数,以此类推,直到end == 0
java
/**
* 排序方式:堆排序
* 时间复杂度:O(N * log2(N))
* 稳定性:不稳定
* @param array
*/
public static void heapSort(int[] array){
createHeap(array);
int end = array.length - 1;
while (end >= 0){
swap(array,end,0);
siftDown(array,0,end);//确保顶为最大值
end--;
}
System.out.println("堆排序");
}
private static void createHeap(int[] array) {
for(int parent = (array.length - 2) / 2; parent >= 0; parent--){
siftDown(array,parent,array.length);
}
}
private 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;
}
}
}
三、交换排序
3.1 冒泡排序
冒泡排序都不陌生了,这里就不多赘述了
java
/**
* 排序方式:冒泡排序
* 时间复杂度:O(n^2)
* 稳定性:稳定
* @param array
*/
public static void bubbleSort(int[] array){
for (int i = 1; i < array.length; i++) {//第几次比较
boolean flag = true;
for (int j = 0; j < array.length - i; j++) {
if(array[j] > array[j+1]){
swap(array,j,j+1);
flag = false;
}
}
if(flag){
break;
}
}
System.out.println("冒泡排序");
}
3.2 快速排序
思路:在数组中确定一个值,将这个数放到一个位置,通过调整这个数组,使得在这个数的左边所有数全部比它小,右边全都比它大,再以这个数的位置作为分界点,去看左半部分和右半部分,最后整个数组有序
hoare法
思路:以左边的数为基准,从右往左走,找比它小的,从左往右走找比它大的,然后两者位置交换
java
/**
* 排序方式:快速排序
* 时间复杂度:O(Nlog2(N))
* 稳定性:不稳定
* @param array
*/
private static void quickSortTree(int[] array, int left, int right) {
if(left >= right){
return;
}
int pivo = partitionHoare(array,left,right);
quickSortTree(array,left,pivo - 1);
quickSortTree(array,pivo + 1,right);
}
private static int partitionHoare(int[] array, int left, int right) {
int tmp = array[left];
int sleft = left;
while (left < right){
while (left < right && array[right] >= tmp){//一直往左走,直到遇到比tmp小的数
right--;
}
while (left < right && array[left] <= tmp){//一直往左走,直到遇到比tmp大的数
left++;
}
swap(array,left,right);
}
swap(array,sleft,left);
return left;
}
基于上面的代码进行的改进,只是理解过程的话,上面的就够了
优化版(进行了一些提升)
java
/**
* 排序方式:快速排序
* 时间复杂度:O(Nlog2(N))
* 稳定性:不稳定
* @param array
*/
public static void quickSort(int[] array){
int left = 0;
int right = array.length - 1;
quickSortTree(array,left,right);
System.out.println("快速排序");
}
private static void quickSortTree(int[] array, int left, int right) {
if(left >= right){
return;
}
if(right - left + 1 <= 10){//此时说明这个数组很小,如果经历了很多此递归,也已经趋于有序,用直接插入排序可以更快些
insertSortRange(array,left,right);
return;
}
int midIndex = getMidIndex(array,left,right);//将中间的数,最左边的数和最右边的数比较一下,找到最小的给左边的位置,这样在后序的
//的交换中,可以更快的将数组排好,因为数组更加趋于整齐了。
swap(array,left,midIndex);
int pivo = partitionHoare(array,left,right);
quickSortTree(array,left,pivo - 1);
quickSortTree(array,pivo + 1,right);
}
private static int getMidIndex(int[] array, int left, int right) {
int mid = (right - left) / 2;
if(array[left] < array[right]){//左边的小于右边
if(array[right] < array[mid]){
return right;//说明右边的最小
}else {
return mid;//说明中间的最小
}
}else {//右边的小于等于左边
if(array[left] < array[mid]){
return left;//说明左边的最小
}else {
return mid;//说明中间的最小
}
}
}
private 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 >= 0; j--) {
if(array[j] > tmp){
array[j + 1] = array[j];
}else {
array[j + 1] = tmp;
break;
}
}
array[j + 1] = tmp;
}
}
private static int partitionHoare(int[] array, int left, int right) {
int tmp = array[left];
int sleft = left;
while (left < right){
while (left < right && array[right] >= tmp){//一直往左走,直到遇到比tmp小的数
right--;
}
while (left < right && array[left] <= tmp){//一直往左走,直到遇到比tmp大的数
left++;
}
swap(array,left,right);
}
swap(array,sleft,left);
return left;
}
挖坑法
与hoare法不同的是,它不是交换,而是直接覆盖
java
public static void quickSort(int[] array){
int left = 0;
int right = array.length - 1;
quickSortTree(array,left,right);
System.out.println("快速排序");
}
private static void quickSortTree(int[] array, int left, int right) {
if(left >= right){
return;
}
if(right - left + 1 <= 10){//此时说明这个数组很小,如果经历了很多此递归,也已经趋于有序,用直接插入排序可以更快些
insertSortRange(array,left,right);
return;
}
int midIndex = getMidIndex(array,left,right);//将中间的数,最左边的数和最右边的数比较一下,找到最小的给左边的位置,这样在后序的
//的交换中,可以更快的将数组排好,因为数组更加趋于整齐了。
swap(array,left,midIndex);
int pivo = partitionPothole(array,left,right);
quickSortTree(array,left,pivo - 1);
quickSortTree(array,pivo + 1,right);
}
private static int getMidIndex(int[] array, int left, int right) {
int mid = (right - left) / 2;
if(array[left] < array[right]){//左边的小于右边
if(array[right] < array[mid]){
return right;//说明右边的最小
}else {
return mid;//说明中间的最小
}
}else {//右边的小于等于左边
if(array[left] < array[mid]){
return left;//说明左边的最小
}else {
return mid;//说明中间的最小
}
}
}
private 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 >= 0; j--) {
if(array[j] > tmp){
array[j + 1] = array[j];
}else {
array[j + 1] = tmp;
break;
}
}
array[j + 1] = tmp;
}
}
private static int partitionPothole(int[] array, int left, int right) {
int tmp = array[left];
while (left < right){
while (left < right && array[right] >= tmp){//一直往左走,直到遇到比tmp小的数
right--;
}
array[left] = array[right];//将这个小的数放到左边去
while (left < right && array[left] <= tmp){//一直往左走,直到遇到比tmp大的数
left++;
}
array[right] = array[left];//将这个大的数放到右边去
}
array[left] = tmp;
return left;
}
前后指针法
java
public static void quickSort(int[] array){
int left = 0;
int right = array.length - 1;
quickSortTree(array,left,right);
System.out.println("快速排序");
}
private static void quickSortTree(int[] array, int left, int right) {
if(left >= right){
return;
}
if(right - left + 1 <= 10){//此时说明这个数组很小,如果经历了很多此递归,也已经趋于有序,用直接插入排序可以更快些
insertSortRange(array,left,right);
return;
}
int midIndex = getMidIndex(array,left,right);//将中间的数,最左边的数和最右边的数比较一下,找到最小的给左边的位置,这样在后序的
//的交换中,可以更快的将数组排好,因为数组更加趋于整齐了。
swap(array,left,midIndex);
int pivo = partitionFastAndSlowPoint(array,left,right);
quickSortTree(array,left,pivo - 1);
quickSortTree(array,pivo + 1,right);
}
private static int getMidIndex(int[] array, int left, int right) {
int mid = (right - left) / 2;
if(array[left] < array[right]){//左边的小于右边
if(array[right] < array[mid]){
return right;//说明右边的最小
}else {
return mid;//说明中间的最小
}
}else {//右边的小于等于左边
if(array[left] < array[mid]){
return left;//说明左边的最小
}else {
return mid;//说明中间的最小
}
}
}
private 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 >= 0; j--) {
if(array[j] > tmp){
array[j + 1] = array[j];
}else {
array[j + 1] = tmp;
break;
}
}
array[j + 1] = tmp;
}
}
private static int partitionFastAndSlowPoint(int[] array, int left, int right){
int pre = left;
int cur = pre + 1;
int tmp = array[left];
while(cur <= right){
if(array[cur] < tmp && array[++pre] != array[cur]){
swap(array,cur,pre);
}
}
swap(array,left,pre);
return pre;
}
不用递归的方式---用栈来实现
java
public static void quickSortNotRecursion(int[] array){
int left = 0;
int right = array.length - 1;
Stack<Integer> stack = new Stack<>();
stack.push(left);
stack.push(right);
while (!stack.isEmpty()){
right = stack.pop();
left = stack.pop();
int pivo = partitionHoare(array,left,right);
if(pivo > left + 1) {
stack.push(left);
stack.push(pivo-1);
}
if(pivo < right-1) { //4:00上课
stack.push(pivo+1);
stack.push(right);
}
}
System.out.println("快速排序非递归");
}
四、归并排序
4.1 归并排序
思路:先将数组拆分,然后一步一步归并排序,从而使每次排序的个数少,减少了排序的时间
java
/**
* 排序方式:归并排序
* 时间复杂度: O(N * log2(N) )
* 稳定性:稳定
* @param array
*/
public static void mergeSort(int[] array){
partitionMergeSort(array,0,array.length - 1);
System.out.println("归并排序");
}
private static void partitionMergeSort(int[] array, int left, int right) {
if(left >= right){
return;
}
int mid = (left + right) / 2;
partitionMergeSort(array,left,mid);
partitionMergeSort(array,mid + 1,right);//进行拆分
merge(array,left,mid,right);//归并
}
private static void merge(int[] array, int left, int mid, int right) {
int[] tmp = new int[right - left + 1];//新建一个数组来存放排序后的数字
int k = 0;//记录tmp的下标
int leftstart = left;
int rightstart = mid + 1;//因为右半边的数组从mid+1开始
while (leftstart <= mid && rightstart <= right){
if(array[leftstart] <= array[rightstart]){
tmp[k++] = array[leftstart++];
}else {
tmp[k++] = array[rightstart++];
}
}
//判断两边数组谁还没有被比较完,没有比较完的插入tmp的后面
while (leftstart <= mid){
tmp[k++] = array[leftstart++];
}
while (rightstart <= right){
tmp[k++] = array[rightstart++];
}
for (int i = 0; i < k; i++) {
array[left + i] = tmp[i];//将排好序的tmp数组放入原本的array数组中,
//这里的i+left是确定在array数组中的位置,即left不是0时
}
}
归并排序非递归
java
public static void mergeSortNotRecursion(int[] array){//类似分治,将数组按一块一块的排序最后再整个排序
int gap = 1;
while (gap < array.length){
for (int i = 0; i < array.length; i += gap * 2) {
int left = i;//i位置为起始位置
int mid = i + 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;
}
System.out.println("归并排序非递归");
}
最后,完整的代码奉上
java
import java.util.Stack;
public class MySort {
/**
* 排序方式:直接插入排序
* 时间复杂度:O(n^2),但当数据趋于有序时,比如有序数组,那么时间复杂度为O(n)
* 稳定性:稳定
* @param array
*/
public static 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 {
array[j + 1] = tmp;
break;
}
}
//走到这有两种可能,一种是已经将tmp放到正确的位置了
//一种是j到了-1的位置tmp是最小值,要将0位置的值改成tmp
array[j + 1] = tmp;
}
System.out.println("直接插入排序");
}
/**
* 排序方式:希尔排序
* 时间复杂度:根据不同的gap时间复杂度不同,可以粗略理解为O(n^1.25)
* 稳定性:不稳定
* @param array
*/
public static void shellSort(int[] array){
int gap = array.length;
while (gap > 1){
gap = gap/3 +1;
shellSortReal(array,gap);
}
System.out.println("希尔排序");
}
private static void shellSortReal(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;
}
}
/**
* 排序方式:直接选择排序
* 时间复杂度:O(n^2)
* 稳定性:不稳定
* @param array
*/
public static void selectSort(int[] array){
int left = 0;
int right = array.length - 1;
while (left < right){
int minIndex = left;
int maxIndex = left;
for (int i = left; i <= right; i++) {
if(array[i] < array[minIndex]){
minIndex = i;
}
if(array[i] > array[maxIndex]){
maxIndex = i;
}
}
swap(array,minIndex,left);
if(maxIndex == left) {//预防出现最大值在left位置,然后left的最大值被换到了minIndex位置
maxIndex = minIndex;
}
swap(array,maxIndex,right);
left++;
right--;
}
System.out.println("直接选择排序");
}
private static void swap(int[] array, int i, int j){
int tmp = array[i];
array[i] = array[j];
array[j] = tmp;
}
/**
* 排序方式:堆排序
* 时间复杂度:O(N * log2(N))
* 稳定性:不稳定
* @param array
*/
public static void heapSort(int[] array){
createHeap(array);
int end = array.length - 1;
while (end >= 0){
swap(array,end,0);
siftDown(array,0,end);//确保顶为最大值
end--;
}
System.out.println("堆排序");
}
private static void createHeap(int[] array) {
for(int parent = (array.length - 2) / 2; parent >= 0; parent--){
siftDown(array,parent,array.length);
}
}
private 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;
}
}
}
/**
* 排序方式:冒泡排序
* 时间复杂度:O(n^2)
* 稳定性:稳定
* @param array
*/
public static void bubbleSort(int[] array){
for (int i = 1; i < array.length; i++) {//第几次比较
boolean flag = true;
for (int j = 0; j < array.length - i; j++) {
if(array[j] > array[j+1]){
swap(array,j,j+1);
flag = false;
}
}
if(flag){
break;
}
}
System.out.println("冒泡排序");
}
/**
* 排序方式:快速排序
* 时间复杂度:O(Nlog2(N))
* 稳定性:不稳定
* @param array
*/
public static void quickSort(int[] array){
int left = 0;
int right = array.length - 1;
quickSortTree(array,left,right);
System.out.println("快速排序");
}
public static void quickSortNotRecursion(int[] array){
int left = 0;
int right = array.length - 1;
Stack<Integer> stack = new Stack<>();
stack.push(left);
stack.push(right);
while (!stack.isEmpty()){
right = stack.pop();
left = stack.pop();
int pivo = partitionHoare(array,left,right);
if(pivo > left + 1) {
stack.push(left);
stack.push(pivo-1);
}
if(pivo < right-1) { //4:00上课
stack.push(pivo+1);
stack.push(right);
}
}
System.out.println("快速排序非递归");
}
private static void quickSortTree(int[] array, int left, int right) {
if(left >= right){
return;
}
if(right - left + 1 <= 10){//此时说明这个数组很小,如果经历了很多此递归,也已经趋于有序,用直接插入排序可以更快些
insertSortRange(array,left,right);
return;
}
int midIndex = getMidIndex(array,left,right);//将中间的数,最左边的数和最右边的数比较一下,找到最小的给左边的位置,这样在后序的
//的交换中,可以更快的将数组排好,因为数组更加趋于整齐了。
swap(array,left,midIndex);
int pivo = partitionFastAndSlowPoint(array,left,right);
quickSortTree(array,left,pivo - 1);
quickSortTree(array,pivo + 1,right);
}
private static int getMidIndex(int[] array, int left, int right) {
int mid = (right - left) / 2;
if(array[left] < array[right]){//左边的小于右边
if(array[right] < array[mid]){
return right;//说明右边的最小
}else {
return mid;//说明中间的最小
}
}else {//右边的小于等于左边
if(array[left] < array[mid]){
return left;//说明左边的最小
}else {
return mid;//说明中间的最小
}
}
}
private 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 >= 0; j--) {
if(array[j] > tmp){
array[j + 1] = array[j];
}else {
array[j + 1] = tmp;
break;
}
}
array[j + 1] = tmp;
}
}
private static int partitionFastAndSlowPoint(int[] array, int left, int right){
int pre = left;
int cur = pre + 1;
int tmp = array[left];
while(cur <= right){
if(array[cur] < tmp && array[++pre] != array[cur]){
swap(array,cur,pre);
}
}
swap(array,left,pre);
return pre;
}
private static int partitionPothole(int[] array, int left, int right) {
int tmp = array[left];
while (left < right){
while (left < right && array[right] >= tmp){//一直往左走,直到遇到比tmp小的数
right--;
}
array[left] = array[right];//将这个小的数放到左边去
while (left < right && array[left] <= tmp){//一直往左走,直到遇到比tmp大的数
left++;
}
array[right] = array[left];//将这个大的数放到右边去
}
array[left] = tmp;
return left;
}
private static int partitionHoare(int[] array, int left, int right) {
int tmp = array[left];
int sleft = left;
while (left < right){
while (left < right && array[right] >= tmp){//一直往左走,直到遇到比tmp小的数
right--;
}
while (left < right && array[left] <= tmp){//一直往左走,直到遇到比tmp大的数
left++;
}
swap(array,left,right);
}
swap(array,sleft,left);
return left;
}
/**
* 排序方式:归并排序
* 时间复杂度: O(N * log2(N) )
* 稳定性:稳定
* @param array
*/
public static void mergeSort(int[] array){
partitionMergeSort(array,0,array.length - 1);
System.out.println("归并排序");
}
public static void mergeSortNotRecursion(int[] array){//类似分治,将数组按一块一块的排序最后再整个排序
int gap = 1;
while (gap < array.length){
for (int i = 0; i < array.length; i += gap * 2) {
int left = i;//i位置为起始位置
int mid = i + 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;
}
System.out.println("归并排序非递归");
}
private static void partitionMergeSort(int[] array, int left, int right) {
if(left >= right){
return;
}
int mid = (left + right) / 2;
partitionMergeSort(array,left,mid);
partitionMergeSort(array,mid + 1,right);//进行拆分
merge(array,left,mid,right);//归并
}
private static void merge(int[] array, int left, int mid, int right) {
int[] tmp = new int[right - left + 1];//新建一个数组来存放排序后的数字
int k = 0;//记录tmp的下标
int leftstart = left;
int rightstart = mid + 1;//因为右半边的数组从mid+1开始
while (leftstart <= mid && rightstart <= right){
if(array[leftstart] <= array[rightstart]){
tmp[k++] = array[leftstart++];
}else {
tmp[k++] = array[rightstart++];
}
}
//判断两边数组谁还没有被比较完,没有比较完的插入tmp的后面
while (leftstart <= mid){
tmp[k++] = array[leftstart++];
}
while (rightstart <= right){
tmp[k++] = array[rightstart++];
}
for (int i = 0; i < k; i++) {
array[left + i] = tmp[i];//将排好序的tmp数组放入原本的array数组中,这里的i+left是确定在array数组中的位置,即left不是0时
}
}
}
五、有关各个排序的复杂度
总结
本篇文章主要介绍了七种比较常见的排序方式,包括插入、希尔、选择、堆、冒泡、快速、归并这几种,如果有什么不严谨不正确的地方,还望指正,谢谢大家!