数据结构——排序

目录

一、概念

1、定义

2、稳定性

二、插入排序

1、定义

2、直接插入排序

(1).操作

(2).代码

(3).性质

3、希尔排序(缩小增量排序)

(1).操作

(2).代码

(3).性质

三、选择排序

1、定义

2、直接选择排序

(1).操作

(2).代码

(3).性质

3、堆排序

(1).操作

(2).代码

(3).性质

四、交换排序

1、定义

2、冒泡排序

(1).操作

(2).代码

(3).性质

3、快速排序

(1).操作

(2).代码

(4).性质

(5).优化快速排序

(6).非递归实现快速排序

五、归并排序

1、定义

2、操作

3、代码

4、性质

5、非递归的归并排序

六、常见排序算法复杂度及稳定性

七、其他非基于比较排序

1、计数排序

(1).操作

(2).代码

(3).性质

2、基数排序

3、桶排序


一、概念

1、定义

排序就是使一堆元素,按照递增或递减的顺序排列。

  • 内部排序:数据元素全部放在内存中。
  • 外部排序:数据元素过多不能同时放在内存中,需要在磁盘等外部存储进行的排序。(根据排序过程的要求,不能在内外存之间移动数据的排序)

2、稳定性

在一组待排序的元素中存在多个相同的元素,经过排序后这些元素的相对次序保持不变,就称这个排序算法稳定,否则不稳定。

 

二、插入排序

1、定义

插入排序是将等待排序的元素按大小插入到一个已经排序好的有序序列中,直到所有等待排序的元素插完为止。

2、直接插入排序

(1).操作

当插入第i(i>=1)个元素时,前面的0至i-1个元素已经排好序,此时用array[i]与前0至i-1个元素进行比较,找到合适位置插入,原来位置上的元素依次往后移。

(2).代码

public static void insertSort(int[] array){
    for(int i=1;i<array.length;i++){
        int tmp=array[i];
        int j=i-1;
        while(j>=0){
            if(array[j]>tmp){
                array[j+1]=array[j];
            }else{
                break;
            }
            j--;
        }
        array[j+1]=tmp;
    }
}

(3).性质

  • 稳定性:稳定
  • 时间复杂度:O(N^2)
  • 空间复杂度:O(1)

注:元素集合越接近有序,直接插入排序算法的时间效率越高

3、希尔排序(缩小增量排序)

(1).操作

先选定一个整数,把待排序元素中所有元素分成多个组,所有距离为该整数的元素分在同一组内,并对每组内的元素进行排序。然后再重复以上过程,直到之前选的整数减为1,此时所有元素完成排序。

(2).代码

public static void shellSort(int[] array){
    int gap=array.length;
    while(gap>0){
        shell(array,gap);
        gap/=2;
    }
}
public static void shell(int[] array,int gap){
    for(int i=gap;i<array.length;i++){
        int tmp=array[i];
        int j=i-gap;
        while(j>=0){
            if(array[j]>tmp){
                array[j+gap]=array[j];
            }else{
                break;
            }    
            j-=gap;
        }
        array[j+gap]=tmp;
    }
}

(3).性质

稳定性:不稳定

时间复杂度:gap取值方式多样希尔排序时间复杂度很难计算,但因为gap是按照Knuth提出的取值方式,而且Knuth进行了大量的实验统计,因此时间复杂度暂定为O(n^1.25)~O(1.6*n^1.25)。

注:希尔排序是对直接插入排序的优化;当gap>1时都是预排序,目的是让数组更接近于有序。当gap=1时,数组已经接近于有序,所以完成排序会很快。

三、选择排序

1、定义

每次从待排序的数据中选出最小的一个元素,依次存放在序列的起始位置,直到待排元素全部排完。

2、直接选择排序

(1).操作

  1. 在待排元素集合中选取一个最小的元素。
  2. 如果该元素不是这组元素中的第一个元素,则将它与第一个元素交换位置。
  3. 在剩下的元素集合中(arr[i+1]~arr[n-1])中重复以上过程,直到集合剩余一个元素

(2).代码

public static void selectSort(int[] array){
    for(int i=0;i<array.length;i++){
        int minIndex=i;
        int j=i+1;
        while(j<array.length){
            if(array[j]<array[minIndex]){
                minIndex=j;
            }
            j++;
        }
        swap(array,i,minIndex);
    }
}

(3).性质

  • 稳定性:不稳定
  • 时间复杂度:O(N^2)
  • 空间复杂度:O(1)

注:直接选择排序效率不是很好,实际中很少用到。

3、堆排序

堆排序是利用堆积树这种数据结构的一种排序算法,是通过堆来进行数据选择,排升序建大堆,排降序建小堆

(1).操作

以升序为例,先用集合元素建一个堆,然后通过向下调整将最大的数放到顶部,其次将顶部元素与最后一个元素互换并且usedSize-1,再次重复以上过程,直到usedSize=1时完成排序。

(2).代码

public static void heapSort(int[] array){
    creatBigHeap(array);
    int end=usedSize-1;
    while(end>0){
        swap(array,0,end);
        shiftDown(array,0,end);
        end--;
    }
}
public static void createBigSort(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){
        if(child+1<len&&array[child]<array[child+1]){
            child++;
        }
        if(array[child]>array[parent]){
            swap(array,child,parent);
            parent=child;
            child=parent*2+1;
        }else{
            break;
        }
    }
}

(3).性质

  • 稳定性:不稳定
  • 时间复杂度:O(N*logN)
  • 空间复杂度:O(1)

四、交换排序

1、定义

将值较大的元素向尾部移,将值较小的元素向头部移

2、冒泡排序

(1).操作

从一堆元素中找到最大的一个,然后放到尾部,再在剩下的集合中重复以上过程,直到集合剩下一个元素时,完成排序。

(2).代码

public static void bubbleSort(int[] array){
    for(int i=0;i<array.length-1;i++){
        boolean flag=true;
        for(int j=0;j<array.length-1-i;j++){
            if(array[j]>array[j+1]){
                swap(array,j,j+1);
                flag=false;
            }
        }
        if(flag){
            return;
        }
    }
}

(3).性质

  • 稳定性:稳定
  • 时间复杂度:O(N^2)
  • 空间复杂度:O(1)

3、快速排序

快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法

(1).操作

任取待排序元素集合中的某个元素作为基准值,按照该基准值将集合分割成两个子序列,左子序列中的所有元素均小于该基准值,右序列中的所有元素均大于该基准值,然后左右子序列重复以上过程,直到完成排序。

(2).代码

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 pivot=partition(array,start,end);
    quick(array,start,pivot-1);
    quick(array,pivot+1;end);
}

【1】.Hoare法

public static int partition(int[] array,int left,int right){
    int tmp=array[left];
    int i=left;
    while(left<right){
        while(left<right&&array[right]>=tmp){
            right--;
        }
        while(left<right&&array[left]<=tmp){
             left++;   
        }
        swap(array,left,right);
    }
    swap(array,i,left);
    return left;
}

【2】.挖坑法

public 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;
}

【3】.前后指针法

public static int partition(int[] array,int left,int right){
    int prev=left;
    int cur=left+1;
    while(cur<=right){
        if(array[cur]<array[left]&&array[++prev]!=array[cur]){
            swap(array,cur,prev);
        }
        cur++;
    }        
    swap(array,prev,left);
    return prev;
}

(4).性质

  • 稳定性:不稳定
  • 时间复杂度:O(N*logN)
  •  空间复杂度:O(logN)

(5).优化快速排序

【1】.三数取中法选取key

private static int midThree(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[right]){
            return right;
        }else if(array[mid]>array[left]){
            return left;
        }else {
            return mid;
        }
    }
}

【2】.递归到小的子区间时,使用插入排序

public static void quick(int[] array,int start,int end){
	if(start>=end){
		return;
	}
	//使用这个优化主要是解决减少递归的次数
	if(end-start+1<=14){
		//插入排序
		insertSort2(array,start,end);
		return;
	}
	//三数取中法
	int index=midThree(array,start,end);
	swap(array,index,start);
	int pivot=partition(array,start,end);
	quick(array,start,pivot-1);
	quick(array,pivot+1,end);
}

(6).非递归实现快速排序

public static quickSort2(int[] array){
    Deque<Integer> stack=new LinkedList<>();
    int left=0;
    int right=array.length-1;
    int pivot=partition(array,left,right);
    if(pivot>left+1){
        stack.push(left);
        stack.push(pivot-1);
    }
    if(pivot<right-1){
        stack.push(pivot+1);
        stack.push(right);
    }
    while(!stack.isEmpty()){
        right=stack.pop();
        left=stack.pop();
        pivot=partition(array,left,right);
        if(pivot>left+1){
            stack.push(left);
            stack.push(pivot-1);
        }
        if(pivot<right-1){
            stack.push(pivot+1);
            stack.push(right);
        }
    }    
}

五、归并排序

1、定义

归并排序是建立在归并操作上的一种排序算法,该算法采用的是分治法。

2、操作

先将序列分解成多个单数子序列,再将子序列合并成有序序列(将两个有序表合并成一个有序表,称为二路归并)。

3、代码

public static void mergeSort(int[] array){
    mergeSortFunc(array,0,array.length-1);
}
public static void mergeSortFunc(int[] array,int left,int right){
    if(left>=right){
        return;
    }
    int mid=(left+right)/2;
    mergeSortFunc(array,left,mid);
    mergeSortFunc(array,mid+1,right);
    merge(array,left,right,mid);
}
public static void merge(int[] array,int start,int end,int mid){
    int s1=start;
    int s2=mid+1;
    int[] tmp=new int[end-start+1];
    int k=0;
    while(s1<=mid&&s2<=end){
        if(array[s1]<=array[s2]){
            tmp[k++]=array[s1++];
        }else{
            tmp[k++]=array[s2++];
        }
    }
    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];
    }
}

4、性质

  • 稳定性:稳定
  • 时间复杂度:O(N*logN)
  • 空间复杂度:O(N)

注:归并排序的缺点在于需要O(N)的空间复杂度,归并排序更多是用来解决磁盘中的外排序问题。在大量数据的排序中,内存无法将所有数据全部放下,所以需要外部排序,而归并排序就是最常用的外部排序

5、非递归的归并排序

public static void mergeSort2(int[] array){
	int gap=1;
	while(gap<array.length){
		// i += gap * 2 当前gap组的时候,去排序下一组
		for(int i=0;i<array.length;i+=gap*2){
			int left=i;
			int mid=left+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,right,mid);
		}
		//当前为2组有序  下次变成4组有序
		gap*=2;
	}
}

六、常见排序算法复杂度及稳定性

排序方法最好平均最坏空间复杂度稳定性
冒泡排序O(N)O(N^2)O(N^2)O(1)稳定
插入排序O(N)O(N^2)O(N^2)O(1)稳定
选择排序O(N^2)O(N^2)O(N^2)O(1)不稳定
希尔排序O(N)O(N^1.3)O(N^2)O(1)不稳定
堆排序O(N*logN)O(N*logN)O(N*logN)O(1)不稳定
快速排序O(N*logN)O(N*logN)O(N^2)O(logN)~O(N)不稳定
归并排序O(N*logN)O(N*logN)O(N*logN)O(N)稳定

七、其他非基于比较排序

1、计数排序

计数排序又称为鸽巢原理,是对哈希直接定址法的变形应用

(1).操作

  1. 统计相同元素出现的次数
  2. 根据统计的结果将序列回收到原来的序列中

(2).代码

public static void countSort(int[] array){
    //找到数组中的最大值和最小值
    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];
        }
    }
    //根据范围定义计数数组的长度
    int len=max-min+1;
    int[] count=new int[len];
    //遍历数组,在计数数组中记录每个数字出现的次数
    for(int i=0;i<array.length;i++){
        count[array[i]-min]++;
    }
    //遍历计数数组
    int index=0;
    for(int i=0;i<count.length;i++){
        while(count[i]>0){
            array[index]=i+min;
            index++;
            count[i]--;
        }
    }
}

(3).性质

  • 稳定性:稳定
  • 时间复杂度:O(MAX(N,范围))
  • 空间复杂度:O(范围)

2、基数排序

3、桶排序

 

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值