概述
常见七大排序算法
- 排序概念
- 几种常见排序
- (1)插入排序
- 直接插入排序
- 希尔排序
- (2)选择排序
- 直接选择排序
- 堆排序
- (3)交换排序
- 冒泡排序
- 快速排序
- 递归实现快速排序
- 非递归实现快速排序
- (4)归并排序
- 归并排序
- 递归实现归并排序
- 非递归实现归并排序
- 桶排序(了解)
- 总结
排序概念
排序
,就是使一串记录按照其中的某个或某些关键字的大小,进行递增或递减的排列;
几种常见排序
- 按稳定性划分
稳定性
:简单来说就是看在排序的过程中,有没有间隔进行交换或者插入,有就是不稳定,没有间隔进行插入或交换就是稳定;
- 按排序思想划分
内部排序:将数据元素全部加载到内存中的排序;
外部排序:数据元素没有全部加载到内存中进行排序,
(1)插入排序
基本思想
:把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列;
直接插入排序
排序方法:当插入第
i(i>=1)
个元素时,前面的i-1
个元素已经是有序序列了,此时只需要将array[i]
的排序码与array[i-1],array[i-2],
…的排序码顺序进行比较,找到插入位置将array[i]
插入,将原来位置上的元素顺序后移;
代码实现:
public class Sort {
//打印方法
public static void print( int[] array){
for(int i=0;i<array.length;i++){
System.out.print(array[i] + " ");
}
System.out.println();
}
public static void insertSort(int[] array){
//先取到数组中的每个元素
for(int i=1;i<array.length;i++){
//将该元素插入到排序序列中
int key = array[i];
int end=i-1;
while(end>=0 && key < array[end]){
array[end+1]=array[end];
end--;
}
array[end+1]=key;
}
}
public static void main(String[] args) {
int[] array={2,3,1,5,4,8,9,6,7};
print(array);
insertSort(array);
print(array);
}
}
输出结果:
性能分析
:
- 时间复杂度:O(N^2)
- 空间复杂度:O(1)
- 稳定性:稳定
使用场景
:元素集合越 “接近有序”,直接插入排序算法的时间效率越高;
接近有序:一组序列中,小的数据尽量靠前,大的数据尽量靠后,不大不小的数据靠中间;
希尔排序
希尔排序法又称
缩小增量法
。它的基本思想是:先选定一个整数 gap,把待排序文件中所有记录分成组,所有距离为gap的记录分在同一组内,并对每一组内的记录进行排序。重复上述分组和排序的工作。当到达gap=1时,所有记录在统一组内排好序;
简单来说就是,先分组,在运用直接插入排序
;
假设升序排序:
初始数据:
- 令 gap=3,将整个数组分为以下三组
- 针对每一组进行插入排序
第一组排序结果:7 8 9
第二组排序结果:1 3 5
第三组排序结果:2 4 6
整体这一次排序结果:
- 令 gap=2,将整个数组分为以下两组
针对每组进行排序:
第一组排序结果:2 3 6 7 9
第二组排序结果:1 4 5 8
整体结果:
- 令 gap=1,将整个数组就排好序啦~,结果如下:
由上可知,希尔排序与gap的取值有关,常见的取值有:size/2, gap=gap/3+1(Knuth提出)…
代码实现:
public class Sort {
public static void print( int[] array){
for(int i=0;i<array.length;i++){
System.out.print(array[i] + " ");
}
System.out.println();
}
public static void shellSort(int[] array){
int gap=array.length;
while(gap>1){
gap=gap/3+1;
for(int i=gap;i<array.length;i++){
//将该元素插入到排序序列中
int key = array[i];
int end=i-gap;
while(end>=0 && key<array[end]){
array[end+gap]=array[end];
end-=gap;
}
array[end+gap]=key;
}
}
}
public static void main(String[] args) {
int[] array={9,5,2,7,3,6,8,1,4};
print(array);
shellSort(array);
print(array);
}
}
结果输出:
性能分析
:
- 时间复杂度:O(N1.25)~(1.6*N1.25)
(原因:我们的 gap取值为 gap/3+1) - 空间复杂度:O(1)
- 稳定性:不稳定
使用场景
:元素量大且比较随机的场合;
(2)选择排序
基本思想
:每一次从待排序的数据元素中取出最小(或最大)的一个元素,存放在序列的起始或末尾位置,直到全部待排序的数据均以排完序;
直接选择排序
假设排升序
:
- 找序列中最大元素所处的位置
- 将该元素与区间最后一个元素进行交换
- 重复循环以上两步骤
代码实现:
public class Sort {
public static void print( int[] array){
for(int i=0;i<array.length;i++){
System.out.print(array[i] + " ");
}
System.out.println();
}
public static void selectSort(int[] array){
int size=array.length;
//控制选择的次数
for(int i=0;i<size;i++){
//控制选择的方式
//找最大元素或者最小元素所在的位置
int pos = 0; //pos来标记最大元素的位置
//控制排序的方式
for(int j=1;j<size-i;j++){
if(array[j] > array[pos]){
pos=j;
}
}
//找到最大元素的下标,就是pos所标记的元素
//将该位置上的元素与区间最后一个元素进行交换
if(pos!=size-1-i){
swap(array,pos,size-i-1);
}
}
}
public static void main(String[] args) {
int[] array={9,5,2,7,3,6,8,1,4};
print(array);
selectSort(array);
print(array);
}
}
结果输出:
性能分析
:
- 时间复杂度:O(N^2)
- 空间复杂度:O(1)
- 稳定性:不稳定
效率不是很好,实际中很少使用;
优化:一次性找出最大和最小元素的下标;
public static void selectSortOP(int[] array){
int begin=0;
int end=array.length-1;
while(begin<end){
//找最大、最小两个元素的位置
int minPos=begin; //标记最小元素的位置
int maxPos=begin; //标记最大元素的位置
int index=begin+1;
while(index<=end){
if(array[index]>array[maxPos]){
maxPos=index;
}
if(array[index]<array[minPos]){
minPos=index;
}
index++;
}
//已经找到最大和最小元素的下标了
if(maxPos!=end){
swap(array,maxPos,end);
}
if(minPos!=begin){
swap(array,minPos,begin);
}
begin++;
end--;
}
}
堆排序
假设排升序:
升序建大堆,降序建小堆
步骤:
- 建堆:
建堆方法:找倒数第一个非叶子结点,从该结点开始直到根结点,利用向下调整;
- 利用堆删除的思想排序
方法:堆顶元素与堆中最后一个元素交换
将堆中有效元素的个数减少一个;
将堆顶元素向下调整;
代码实现:
public class Sort {
public static void print( int[] array){
for(int i=0;i<array.length;i++){
System.out.print(array[i] + " ");
}
System.out.println();
}
//交换方法
public static void swap(int[] array,int left,int right){
int temp=array[left];
array[left]=array[right];
array[right]=temp;
}
public static void shiftDown(int[] array,int size,int parent){
int child=2*parent+1; //标记左孩子
//int size=array.length;
//找左右孩子中较大的孩子
while(child<size){
if( child+1 <size && array[child]<array[child+1]){
child=child+1;
}
//检测是否满足堆的特性
if(array[child]>array[parent]){
swap(array,child,parent);
parent=child;
child=parent*2+1;
}else{
return;
}
}
}
public static void heapSort(int[] array){
//建堆---->取决于排序的方式
//升序:建大堆 降序:建小堆
int size=array.length;
//建堆的方法:1,找倒数第一个不饱和的非叶子结点; 2,然后进行向下调整
int lastNodeParent=(size-2)/2;
for(int root=lastNodeParent;root>=0;root--){
shiftDown(array,size,root);
}
//利用堆删除的思想排序
int end=size-1;
while(end>0){
//1.堆顶元素与堆中最后一个元素交换
swap(array,0,end);
//2.将堆中元素减少一个
//3.将堆顶元素向下调整
shiftDown(array,end,0);
end--;
}
}
public static void main(String[] args) {
int[] array={9,5,2,7,3,6,8,1,4};
print(array);
heapSort(array);
print(array);
}
}
结果输出:
性能分析
:
- 时间复杂度:O(NlogN)
- 空间复杂度:O(1)
- 稳定性:不稳定
使用场景:top-k问题常选
(3)交换排序
所谓交换就是根据序列中两个记录键值的比较结果来对换这两个记录在序列中的位置;
冒泡排序
步骤:
- 相邻两个元素对比
- 不满足排序要求的就进行交换
依次往下进行比较并排序就可以排好了;
代码实现:
public class Sort {
public static void print( int[] array){
for(int i=0;i<array.length;i++){
System.out.print(array[i] + " ");
}
System.out.println();
}
//交换方法
public static void swap(int[] array,int left,int right){
int temp=array[left];
array[left]=array[right];
array[right]=temp;
}
public static void bubbleSort(int[] array){
int size=array.length;
//外层循环控制冒泡的趟数,减1,最后一趟只有一个元素,不用冒泡
for(int i=0;i<size-1;i++){
for(int j=1;j<size;j++){
//控制冒泡的方式
//j=1表示后一个元素的下标
if(array[j]<array[j-1]){
swap(array,j,j-1);
}
}
}
}
public static void main(String[] args) {
int[] array={9,5,2,7,3,6,8,1,4};
print(array);
bubbleSort(array);
print(array);
}
}
结果输出:
性能分析
:
- 时间复杂度:O(N^2)
- 空间复杂度:O(1)
- 稳定性:稳定
当前面数据已经排好序时,就不用进行交换了这样就减少了冒泡的次数,我们可以加一个标志位进行优化;
代码实现:
public static void bubbleSortOP(int[] array){
//当元素已经排好序时,就不用进行交换
int size=array.length;
//外层循环控制冒泡的趟数,减1,最后一趟只有一个元素,不用冒泡
for(int i=0;i<size-1;i++){
boolean isChange=false;
for(int j=1;j<size;j++){
//控制冒泡的方式
//j=1表示后一个元素的下标
if(array[j]<array[j-1]){
swap(array,j,j-1);
isChange=true;
}
}
if(!isChange){
return;
}
}
}
快速排序
快速排序是
Hoare
于1962年提出的一种二叉树结构的交换排序方法;
递归实现快速排序
假设排升序
:
步骤:
- 找一个
基准值
,将排序序列分为基准值的两侧,左侧数据均小于基准值,右侧数据均大于基准值; - 递归排基准值的左侧序列
- 递归排基准值的右侧序列
递归实现:
public static void quickSort(int[] array,int left,int right){
//找一个基准值
//以基准值将区间[left ,right)分为两个部分
if(right-left>1){
//说明区间还有元素
//分割整个区间,将基准值下标返回
int div=partition(array,left,right);
//递归排基准值的左侧序列[left,div)
quickSort(array,left,div);
//递归排基准值的右侧序列[div+1,right)
quickSort(array,div+1,right);
}
}
分割的三种方式:
方式一
:
Hoare 提出的一种方式;
为了实现代码方便,取最右侧 key=5
为基准值;然后让 begin
从前往后找比基准值大的元素,找到之后停下来,让end
从后往前找比基准值小的元素,找到之后停下来,当begin
不等于end
时,就交换;
交换之后的数据就为:
循环继续,最后将基准值位置放好即可;
代码实现:
//交换方法
public static void swap(int[] array,int left,int right){
int temp=array[left];
array[left]=array[right];
array[right]=temp;
}
public static int partition(int[] array,int left,int right){
int key=array[right-1]; //基准值
int begin=left;
int end=right-1;
while(begin<end){
//begin从前往后找比key大的元素,找到之后停下来
while(begin<end && array[begin]<=key){
begin++;
}
//end从后往前找比key小的元素,找到之后停下来
while(begin<end && array[end]>=key){
end--;
}
//begin和end在同一个位置,就不用交换了
if(begin!=end){
swap(array,begin,end);
}
}
if(begin!=right-1){
swap(array,begin,right-1);
}
return begin;
}
方式二
:挖坑法
- 取最右侧位置为基准值,取出之后该位置就是一个坑位;
begin
从前往后找比基准值大的元素,找到之后填基准值挖走的坑位;end
从后往前找比基准值小的元素,找到之后填begin
的坑位;- 循环进行;
代码实现:
public static int partition(int[] array,int left,int right) {
int key = array[right - 1];
int begin = left;
int end = right-1 ;
while (begin < end) {
//begin 从前往后找基准值大的元素
while (begin < end && array[begin] <= key) {
begin++;
}
if (begin < end) {
array[end] = array[begin];
}
//end 从后往前找比基准值小的元素
while (begin < end && array[end] >= key) {
end--;
}
if (begin < end) {
array[begin] = array[end];
}
}
array[begin] = key;
return begin;
}
方式三
:前后引用
- 取最后一个元素为基准值
cur
指向最左侧元素,prev
指向cur
的前一个元素cur
和prev
从前往后找比基准值小的元素,找到之后,prev
往前走一步,然后进行比较,两个不相等则进行交换;- 最后,将基准值放置好;
不理解直接看代码吧~~
代码实现:
//交换方法
public static void swap(int[] array,int left,int right){
int temp=array[left];
array[left]=array[right];
array[right]=temp;
}
public static int partition(int[] array,int left,int right){
int key=array[right-1];
int cur=left;
int prev=cur-1;
while(cur<right){
//让cur 从前往后找比key小的元素
if(array[cur]<key && ++prev != cur){
swap(array,cur,prev);
}
++cur;
}
//放置好基准值的位置
if(++prev!=right-1){
swap(array,prev,right-1);
}
return prev;
}
在上述中,我们将右侧的数据作为基准值,这样就会导致取到极值的概率增大;
思考:下面一组序列
当取key=1
时,最终分割下来就变成了
当数据量为N时,时间复杂度也就是O(N^2)了。
优化的办法:三数取中法
来减少取极值的概率:
三数取中法
代码实现:
//三数取中法
public static int getMiddleIndex(int[] array,int left,int right){
int mid =left+((right-left)>>1);
if(array[left]< array[right-1]){
if(array[mid]<array[left]){
return left;
}else if(array[mid]>array[right-1]){
return right-1;
}else{
return mid;
}
}else{
if(array[mid]>array[left]){
return left;
}else if(array[mid]<array[right-1]){
return right-1;
}else{
return mid;
}
}
}
将此代码运用到我们取基准值的方法体中,就可以避免取到极值的概率;为了使之前的代码改动小,将找到的基准值放在区间末尾即可;其他两种方式写的方法是一样的哦~
如下所示:
public static int partition1(int[] array,int left,int right){
//分割区间
int index=getMiddleIndex(array,left,right);
if(index!=right-1){
swap(array,index,right-1);
}
int key=array[right-1]; //基准值
int begin=left;
int end=right-1;
while(begin<end){
//begin从前往后找比key大的元素,找到之后停下来
while(begin<end && array[begin]<=key){
begin++;
}
//end从后往前找比key小的元素,找到之后停下来
while(begin<end && array[end]>=key){
end--;
}
//begin和end在同一个位置,就不用交换了
if(begin!=end){
swap(array,begin,end);
}
}
if(begin!=right-1){
swap(array,begin,right-1);
}
return begin;
}
性能分析
:
- 时间复杂度:O(Nlog2^N)
原因:
假设经过三数取中法后,每次取到的基准值都是较理想的中间值,那么,该序列数据就变成了一棵平衡树;分割算法的时间复杂度为O(N)
,树的高度为log2N
,所以整个算法的时间复杂度就为O(Nlog2^N)
;
- 空间复杂度:O(1)
- 稳定性:不稳定
非递归实现快速排序
借助栈来实现
;
代码实现:
public static void quickSortNor(int[] array){
//快排的非递归实现--->借助栈
Stack<Integer> s=new Stack<>();
s.push(array.length);
s.push(0);
while(!s.empty()){
int left=s.pop();
int right=s.pop();
if(right-left>1){
int div=partition1(array,left,right);
//将区间分成div的两侧区间
//左区间:[left,div)
//右区间:[div+1,right)
//将右侧区间压入栈中
s.push(right);
s.push(div+1);
//将左侧区间压入栈中
s.push(div);
s.push(left);
}
}
}
结果输出:
(4)归并排序
归并排序
归并排序
(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用,将已有序的子序列合并,得到完全有序的序列;
注意:
归并排序是一个外部排序的算法,不用将所有的数据加载到内存中
;
递归实现归并排序
核心
:先分解(一定是平均分割的),后合并
;
步骤:
- 均分整个序列
- 递归归并左序列
- 递归归并右序列
代码实现:
//将左右两个区间数据合并
public static void mergeData(int[] array,int left,int mid,int right,int[] temp){
int begin1=left,end1=mid;
int begin2=mid,end2=right;
int index=left;
while(begin1<end1 && begin2<end2){
if(array[begin1]<=array[begin2]){
//说明左边区间元素小
temp[index++]=array[begin1++];
}else{
//说明右边区间元素小
temp[index++]=array[begin2++];
}
}
//判断左区间是否还有元素
while(begin1<end1){
temp[index++]=array[begin1++];
}
//判断右区间是否还有元素
while(begin2<end2){
temp[index++]=array[begin2++];
}
}
private static void mergeSort(int[] array,int left,int right,int[] temp){
if(right-left>1){
//先均分[left,right)区间中的元素
int mid=left+((right-left)>>1);
//左区间[left,mid)
mergeSort(array,left,mid,temp);
//右区间[mid,right)
mergeSort(array,mid,right,temp);
mergeData(array,left,mid,right,temp);
System.arraycopy(temp,left,array,left,right-left);
}
//减少传参的风险
public static void mergeSort(int[] array){
int size=array.length;
int[] temp=new int[size];
mergeSort(array,0,size,temp);
}
性能分析
:
-
时间复杂度:O(N*log2N)
-
空间复杂度:O(N)
-
稳定性:稳定
非递归实现归并排序
思路:
将区间中的元素进行均分,直到区间只剩下一个元素时,进行归并
;
代码实现:
//将左右两个区间数据合并
public static void mergeData(int[] array,int left,int mid,int right,int[] temp){
int begin1=left,end1=mid;
int begin2=mid,end2=right;
int index=left;
while(begin1<end1 && begin2<end2){
if(array[begin1]<=array[begin2]){
//说明左边区间元素小
temp[index++]=array[begin1++];
}else{
//说明右边区间元素小
temp[index++]=array[begin2++];
}
}
//判断左区间是否还有元素
while(begin1<end1){
temp[index++]=array[begin1++];
}
//判断右区间是否还有元素
while(begin2<end2){
temp[index++]=array[begin2++];
}
}
public static void mergeSortLoop(int[] array){
int size=array.length;
//辅助空间
int[] temp=new int[size];
int gap=1;
while(gap<size){
for(int i=0;i<size;i+=2*gap){
int left=i;
int mid =left+gap;
int right=mid+gap;
//防止越界
if(mid>size){
mid=size;
}
//防止越界
if(right>size){
right=size;
}
mergeData(array,left,mid,right,temp);
}
System.arraycopy(temp,0,array,0,size);
gap*=2;
}
}
结果输出:
桶排序(了解)
与上面7种排序算法不同的是,该排序算法不用进行比较就能排好顺序;
- 借助桶(数组),给桶进行
0
到9
编号,将数据按照个位、十位、百位的先后顺序依次放入桶中,先放个位,再放十位,最后放百位;(具体就是,个位为零的数据放入零号桶中,个位为1的元素放入1号桶中,依次类推
) - 回收,先放入的元素先回收;
- 重复将个位、十位、百位上的数字放好并回收;
总结
最后
以上就是有魅力学姐为你收集整理的常见排序算法集合来了^_^排序概念几种常见排序桶排序(了解)总结的全部内容,希望文章能够帮你解决常见排序算法集合来了^_^排序概念几种常见排序桶排序(了解)总结所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复