常见排序汇总--你都了解吗?
目录
1,排序的比较方法
2,堆排序
3,冒泡排序
4,直接插入排序
5,希尔排序
6,直接选择排序
7,快速排序
8,归并排序
一,排序的比较
我们一般比较排序只要通过判断排序是否是稳定的,然后就是比较时间复杂度和空间复杂度,并且在下面的排序当中也会设计到,具体稳定性:也就是如下图,我们原本红色的5在后面,不过通过排序之后,可能会有以下两种情况,位置不改变也就是稳定的,空间复杂度我们探究的是最坏情况
二,堆排序
核心:堆排序为的是原地进行排序,而不是重现创建空间来排序,这和我下面提到的从小到大排的时候就要建立大根堆,而相反的从大到小排序就要建立小根堆说法一致
堆排序就是利用优先级队列的思想来排序,当我们想要从小到大排的时候就要建立大根堆,而相反的从大到小排序就要建立小根堆,我们就拿从小到大排来举例,我们先要创建一个大根堆,堆顶元素就是所有里面最大的,然后将最后一个元素和堆顶元素进行交换,然后usedsize--,然后调整除刚换下来的数据以外的数据,然后找到次大的,然后调整剩下的n-2个,直到调整完
稳定性:不稳定,因为当我们堆顶元素和最后元素进行交换后,原来本来存在的顺序会进行一个一百八十度翻转
时间复杂度:O(nlogn),通俗理解:一共 n 个元素,每个元素最多下沉 (log n) 层,总代价 (nlog n)。
空间复杂度:O(1),没有创建新的空间
public void creatheap(){
//先找到最后一颗子树的根节点的下标
for (int parent = (usedsize-1-1)/2; parent >=0; parent--) {
shelldown(parent, usedsize);
}
}
//向下调整
private void shelldown(int parent,int usedsize){
//1,先取得左孩子节点的下标
int child=parent*2+1;
//调整到child小于usedsize的时候
while (child<usedsize){
//先取得左右孩子中最大的,并且得保证有右孩子的情况下,没有就直接不用变
if (child+1<usedsize && elem[child]<elem[child+1]){
child++;
}
//现在child里面存储的就是左右孩子里的最大值的下标,然后进行交换
if (elem[child]>elem[parent]){
swap(elem,parent,child);
//只能保证调整了的是正确的,但是还得向下调整保证换完的也是大根堆
parent=child;
child=parent*2+1;
} else {
break;
}
}
}
//从小到大排
public void heapsort(){
int end=usedsize-1;
while (end>0){
swap(elem,0,end);
shelldown(0,end);
end--;
}
}
三,冒泡排序
很常见的一种排序方法,让大的依次放到最后面,跟冒泡一样
public void BubbleSort(int[]array){
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;
}
}
}
}
稳定性:稳定,因为内层循环是严格的判断array[j]>array[j+1]
时间复杂度:O(N^2),外层执行n-1轮,内层最坏依次执行n-1,n-2......总比较次数:((n-1)+(n-2)+...+1 = n(n-1)/2,也就是n^2
空间复杂度:O(1),原地排序
四,直接插入排序
跟打牌把牌插入一样,定义了i,j下标,并且开始j=i-1,并且假设一个零时变量tmp,并且把array[i]的值放入tmp里面,并且和array[j]的值进行比较,然后j--进行回调,直接插入排序的特点:有序度越高,插入排序需要的元素移动次数越少,运行速度越快;完全有序时达到线性最优效率。

//直接插入排序
public 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 {
break;
}
}
array[j+1]=tmp;
}
}
稳定性:稳定,比较逻辑:if(tmp < array[j]) 才移动元素
时间复杂度:O(N^2),内层循环每次都要把前面所有元素往后移
空间复杂度:O(1),原地排序
五,希尔排序
将数据分成很多组来进行排序,如第一次分成五组,每组数据进行排序,接下来再分成两组,组内排序,最后是整组数据一起排序,当然分的组的个数各有说法,可以分成6组可以分成3组,都是可以的,所以我以gap=array.lenth/2来进行分组数,当然组的数据也不能挨着来分,是跳着来分的,目的是为了让大的数据尽可能的往后移

public static void ShellSort(int[]array){
int gap=array.length;
while (gap>1){
gap=gap/2;
shell(array, gap);
}
}
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 {
break;
}
}
array[j+gap]=tmp;
}
}
稳定性:不稳定
时间复杂度:O(N^1.3)-O(N^1.5)
空间复杂度:O(1),原地排序
六,直接选择排序

定义i,j下标和minIndex记录小值的下标,j一直往后走,找到比当前minIndex记录的值还小的值,如果有则minIndex换成j,然后进行交换
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[j]<array[minIndex]){
minIndex=j;
}
}
//开始交换
swap(array,i,minIndex);
}
}
private static void swap(int[]array,int i,int minIndex){
int tmp=array[i];
array[i]=array[minIndex];
array[minIndex]=tmp;
}
稳定性:不稳定
时间复杂度:O(N^2)
空间复杂度:O(1),原地排序
七,快速排序
1,Hoare法

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;
}
//找到中值的下标
int par=pattion(array,start,end);
//左边排序
quick(array,start,par-1);
//右边排序
quick(array,par+1,end);
}
private static int pattion(int[] array, int low, int high) {
//array[low]这个值是一个基准值,通过low以及high以及最后的一个交换,使基准值的左边比他小,右边比他大,让他是一个中值,并且返回中值的下标
int pivot=array[low];
int i=low;
while (low<high){
//high找到比pivot小的值
//low找到比pivot大的值
while (array[high]>=pivot && low<high){
high--;
}
while (array[low]<=pivot && low<high){
low++;
}
swap(array,high,low);
}
//走完这个循环后low和high走到了一起,让这个值的下标和pivot的下标交换
swap(array,i,low);
return low;
}
稳定性:不稳定
时间复杂度:最小O(N*log2N),最大:O(N^2)
空间复杂度:最小O(log2N),最大:O(N)
2,挖坑法

private static int pattion2(int[] array, int low, int high) {
int tmp=array[low];
int i=low;
while (low<high){
//high找到比pivot小的值
//low找到比pivot大的值
while (array[high]>=tmp && low<high){
high--;
}
array[low]=array[high];
while (array[low]<=tmp && low<high){
low++;
}
array[high]=array[low];
}
array[low]=tmp;
return low;
}
3,快排的改善
因为快排的时间复杂度取决于递归的树的高度,因为数量是不变的,所以当我们是完全二叉树的时候,我们树的高度是最小的,为log2N,因此此时的时间复杂度和空间复杂度是最小的,最大的情况就是123,或者321这种有序的数组,因为这样的树就只有左树或者只有右树,导致树的高度最大,为N,所以我们要改善就要尽量使高度减少,用到的方法就是三数取中,找到low下标,high下标和(low+high)/2下标中的最小值,然后将low下标的值和这个值进行交换
private static int threeMid(int[] array,int low, int high) {
int mid = (low+high)/2;
if(array[low] < array[high]) {
if(array[mid] < array[low]) {
return low;
}else if(array[mid] > array[high]) {
return high;
}else {
return mid;
}
}else {
// array[low] > array[high]
if(array[mid] < array[high]) {
return high;
}else if(array[mid] > array[low]) {
return low;
}else {
return mid;
}
}
}
4,快排的非递归实现

public static void QuickSortNor(int array[]){
int start=0;
int end=array.length-1;
int par=pattion2(array,start,end);
Deque<Integer> stack=new LinkedList<>();
if (par>start+1){
stack.push(start);
stack.push(par-1);
}
if (par<end-1){
stack.push(par+1);
stack.push(end);
}
while (!stack.isEmpty()){
end=stack.poll();
start=stack.poll();
par=pattion2(array,start,end);
if (par>start+1){
stack.push(start);
stack.push(par-1);
}
if (par<end-1){
stack.push(par+1);
stack.push(end);
}
}
}
5,两个问题解决

第一个问题:等号是否可以去除
第二个问题:是否可以让low先走 先++
问题一:不可以,因为当我们0下标和array.lenth-1下标的数一样,那么就会两个一样的数一直交换陷入死循环
问题二:不可以,因为我们每次par的目标都是让par左边的数都比par小,右边都比par大,如果让low先走,low是为了找到比par大的数字,最后当low和high相遇的前一步,是low先走的,那么low和high会想遇到low的任务,就是比基准大的数,当基准和这个大的数交换就不能实现左边都小了
八,归并排序
1,正常递归
先把数据整齐的拆分为左右两部分,一直到只剩一个元素的时候,开始逐渐的往上开始合并

public static void MergeSort(int[] array){
mergesort(array,0,array.length-1);
}
private static void mergesort(int[] array, int left, int right) {
if (left>=right){
return;
}
int mid=(left+right)/2;
//左分
mergesort(array,left,mid);
//右分
mergesort(array,mid+1,right);
//合并
marge(array,left,mid,right);
}
private static void marge(int[] array, int left, int mid, int right) {
int[] tmp=new int[right-left+1];
//用于记录数组的下标
int k=0;
int s1=left;
int e1=mid;
int s2=mid+1;
int e2=right;
//保证两段都有数据
while (s1<=e1 && s2<=e2){
if (array[s1]>array[s2]){
tmp[k++]=array[s2++];
} else{
tmp[k++]=array[s1++];
}
}
//这里走完当s1超过了e1或者s2超过了e2,也就是有一段或者两段走完了的,所以可能出现没走完的,就让他直接放在数组里
while (s1<=e1){
tmp[k++]=array[s1++];
}
while (s2<=e2){
tmp[k++]=array[s2++];
}
//下面得将其拷贝在数组里
for (int i = 0; i < tmp.length; i++) {
array[i+left]=tmp[i];
}
}
稳定性:稳定
时间复杂度:O(N*log2N)
空间复杂度:O(N)
2,归并排序的非递归
思想:递归是递归左右两边,为了不递归,那我就要找到左右两边最开始的下标,所以我引进了gap这个变量,并且gap每次乘2,然后left和right每次都是分组的第一个和最后一个,mid每次都是还没分组的最后一个,例如6 3,mid就指向6,四个已经分好的时候1 2 3 6 4,mid就指向6

public static void MergeSort2(int[] array) {
int gap = 1;
while (gap < array.length) {
for (int i = 0; i < array.length; i = i + gap * 2) {
int left = i;
int mid = left + gap - 1;
if (mid > array.length-1 ) {
mid = array.length - 1;
}
int right = mid + gap;
if (right > array.length-1) {
right = array.length - 1;
}
marge(array, left, mid, right);
}
gap *= 2;
}
}
更多推荐
所有评论(0)