数据结构常见的八大排序算法

参考:https://www.cnblogs.com/hokky/p/8529042.html

八大排序,三大查找是《数据结构》当中非常基础的知识点,在这里为了复习顺带总结了一下常见的八种排序算法。常见的八大排序算法,他们之间关系如下:

排序算法.png

 

他们的性能比较:

 

 

性能比较.png


下面,利用Python分别将他们进行实现。

直接插入排序


  • 算法思想:

 

直接插入排序.gif

直接插入排序的核心思想就是:将数组中的所有元素依次跟前面已经排好的元素相比较,如果选择的元素比已排序的元素小,则交换,直到全部元素都比较过。
/*基本思想:将一个数据插入到一组已经排好序的序列当中,插入后该序列依旧是有序的

直接插入排序是一种稳定的排序算法,其时间复杂度为O(n^2),空间复杂度为O(1)

算法思路(从小到大排序):
1、一组数据a[N],设置变量i , j , temp,i 是待插入元素的下标,j是用来搜索比较
2、i 的初始值为1,即从第二个元素开始,第一个元素认为是已经排好序的,所以从第二个元素开始,j的初始值为i
3、将i前面的数据都与a[j]比较,即与待插入数据进行比较,用 j 来移动下标,直到找到一个比待插入数据小的或等于的值,亦或者找到了该序列的尽头(即下标为0)停止,交换他们的位置,退出j循环
4、然后将i=2,从第三个元素开始,重复步骤3,直到 i==N-1停止,即所有数据都已经放到了正确的位置上,该序列称为有序序列
*/

#include<iostream>
using namespace std;

int main(){
   int a[]={70,30,40,10,80,20,90,100,75,60,45};
   int size=sizeof(a)/sizeof(int);
   for (int i=1; i<size;i++) {	    
            for(int j = i;j>0 && a[j-1] > a[j];j--) {
		int tmp = a[j];
		a[j] = a[j-1];
		a[j-1] = tmp; //swap(a[j-1],a[j]);
   	    }
   } 
   for(int i=0;i<size;i++)
        cout<<a[i]<<" ";
   return 0;
}

希尔排序


  • 算法思想:

 

希尔排序.png

 

希尔排序的算法思想:将待排序数组按照步长gap进行分组,然后将每组的元素利用直接插入排序的方法进行排序;每次将gap折半减小,循环上述操作;当gap=1时,利用直接插入,完成排序。
同样的:从上面的描述中我们可以发现:希尔排序的总体实现应该由三个循环完成:

 

这里解释一下,gap=5时,第一组,[8,3],因为3小于8,所以3和8互换,类推

  1. 第一层循环:将gap依次折半,对序列进行分组,直到gap=1
  2. 第二、三层循环:也即直接插入排序所需要的两次循环。具体描述见上。
  • 代码实现:
/*基本思想:
1.希尔排序也是一种插入排序,他是第一个打破时间复杂度O(n^2)的排序方法,它与插入排序的不同之处在于,它会优先比较距离较远的元素。希尔排序又叫缩小增量排序。
2.希尔排序是把元素按下标的一定增量进行分组,对每组使用直接插入排序算法排序(每组每次只插入一个数);
随着增量逐渐减少,当增量减至 1 时,整个文件恰被分成一组,算法便终止
*/

#include<iostream>
using namespace std;
int main(){
   int a[]={9,1,2,5,7,4,8,6,3,5};
   int size=sizeof(a)/sizeof(int);
   for (int gap = size/2;gap>0;gap=gap/2) {       	    
	for (int i=gap; i<size;i++) {	    
	    for(int j = i;j>0 && a[j-1] > a[j];j--) {
		int tmp = a[j];
		a[j] = a[j-1];
		a[j-1] = tmp; //swap(a[j-1],a[j]);
   	    }
	}
   }
   for(int i=0;i<size;i++)
	cout<<a[i]<<" ";
   return 0;
}

简单选择排序


  • 算法思想

 

简单选择排序.gif

简单选择排序的基本思想:比较+交换。

  1. 从待排序序列中,找到关键字最小的元素;
  2. 如果最小元素不是待排序序列的第一个元素,将其和第一个元素互换;
  3. 从余下的 N - 1 个元素中,找出关键字最小的元素,重复(1)、(2)步,直到排序结束。
    因此我们可以发现,简单选择排序也是通过两层循环实现。
    第一层循环:依次遍历序列当中的每一个元素
    第二层循环:将遍历得到的当前元素依次与余下的元素进行比较,符合最小元素的条件,则交换。
  • 代码实现
#include <iostream>
using namespace std;

int main() {
	int a[] = {9,1,2,5,7,4,8,6,3,5};
	int size = sizeof(a)/sizeof(int);
	int value;
	for(int i = 0;i<size;i++){
	    	int min = a[i];
	    for(int j =i+1;j<size;j++){//找出最小下标value
	        if(a[j]<min){
	            min = a[j];
	            value = j;
	        }
	    }
        if(min!=a[i])
	    swap(a[i],a[value]);//和待排序的第一个元素交换
	}
	for(int i =0;i<size;i++)
	cout<<a[i]<<" ";
	return 0;
}

堆排序


  • 堆的概念
    堆:本质是一种数组对象。特别重要的一点性质:<b>任意的叶子节点小于(或大于)它所有的父节点</b>。对此,又分为大顶堆和小顶堆,大顶堆要求节点的元素都要大于其孩子,小顶堆要求节点元素都小于其左右孩子,两者对左右孩子的大小关系不做任何要求。
    利用堆排序,就是基于大顶堆或者小顶堆的一种排序方法。下面,我们通过大顶堆来实现。

  • 基本思想:
    参考https://blog.csdn.net/wfei101/article/details/80615464
    参考https://blog.csdn.net/weixin_42109012/article/details/91668543
    堆排序可以按照以下步骤来完成:

    1. 首先将序列构建称为大顶堆;
      (这样满足了大顶堆那条性质:位于根节点的元素一定是当前序列的最大值)

       

       

      构建大顶堆.png

    2. 取出当前大顶堆的根节点,将其与序列末尾元素进行交换;
      (此时:序列末尾的元素为已排序的最大值;由于交换了元素,当前位于根节点的堆并不一定满足大顶堆的性质)
    3. 对交换后的n-1个序列元素进行调整,使其满足大顶堆的性质;

       

       

      Paste_Image.png

    4. 重复2.3步骤,直至堆中只有1个元素为止
  • 代码实现:

#-------------------------堆排序--------------------------------
#**********获取左右叶子节点**********
def LEFT(i):
    return 2*i + 1
def RIGHT(i):
    return 2*i + 2
#********** 调整大顶堆 **********
#L:待调整序列 length: 序列长度 i:需要调整的结点
def adjust_max_heap(L,length,i):
#定义一个int值保存当前序列最大值的下标
    largest = i
#执行循环操作:两个任务:1 寻找最大值的下标;2.最大值与父节点交换
    while (1):
#获得序列左右叶子节点的下标
        left,right = LEFT(i),RIGHT(i)
#当左叶子节点的下标小于序列长度 并且 左叶子节点的值大于父节点时,将左叶子节点的下标赋值给largest
        if (left < length) and (L[left] > L[i]):
            largest = left
            print('左叶子节点')
        else:
            largest = i
#当右叶子节点的下标小于序列长度 并且 右叶子节点的值大于父节点时,将右叶子节点的下标值赋值给largest
        if (right < length) and (L[right] > L[largest]):
            largest = right
            print('右叶子节点')
#如果largest不等于i 说明当前的父节点不是最大值,需要交换值
        if (largest != i):
            temp = L[i]
            L[i] = L[largest]
            L[largest] = temp
            i = largest
            print(largest)
            continue
        else:
            break
#********** 建立大顶堆 **********
def build_max_heap(L):
    length = len(L)
    for x in range((int)((length-1)/2),-1,-1):
        adjust_max_heap(L,length,x)
#********** 堆排序 **********
def heap_sort(L):
#先建立大顶堆,保证最大值位于根节点;并且父节点的值大于叶子结点
    build_max_heap(L)
#i:当前堆中序列的长度.初始化为序列的长度
    i = len(L)
#执行循环:1. 每次取出堆顶元素置于序列的最后(len-1,len-2,len-3...)
#         2. 调整堆,使其继续满足大顶堆的性质,注意实时修改堆中序列的长度
    while (i > 0):
        temp = L[i-1]
        L[i-1] = L[0]
        L[0] = temp
#堆中序列长度减1
        i = i-1
#调整大顶堆
        adjust_max_heap(L,i,0)

冒泡排序


  • 基本思想

     

     

    冒泡排序.gif

     

    冒泡排序思路比较简单:

    1. 将序列当中的左右元素,依次比较,保证右边的元素始终大于左边的元素;
      ( 第一轮结束后,序列最后一个元素一定是当前序列的最大值;)
    2. 对序列当中剩下的n-1个元素再次执行步骤1。
    3. 对于长度为n的序列,一共需要执行n-1轮比较
      (利用while循环可以减少执行次数)

*代码实现

#include <iostream>
using namespace std;
 
int main() {
	int a[] = {9,1,2,5,7,4,8,6,3,5,0};
	int size = sizeof(a)/sizeof(int);
    for(int i =0;i<size-1;i++)
        for(int j = 0;j<size-1;j++){
            if(a[j]>a[j+1]){
                int tmp = a[j];
                a[j] = a[j+1];
                a[j+1] = tmp;
            }
        }
	for(int i =0;i<size;i++)
	cout<<a[i]<<" ";
	return 0;
}

快速排序


  • 算法思想:

     

    快速排序.gif

    参考:https://www.runoob.com/w3cnote/quick-sort.html
    快速排序的基本思想:挖坑填数+分治法
    1. 从序列当中选择一个基准数,用x来存储它
      在这里我们选择序列当中第一个数i=0最为基准数
    2. 让i=l,j=r(l是数组的第一个数下标,r是最后一个数下标)
    3. 将序列当中的所有数依次遍历,比基准数x大或者等于的位于其右侧,比基准数x小的位于其左侧
    4. 重复步骤1.2,直到所有子集当中只有一个元素为止(即)。
      用伪代码描述如下:
      1.i =L; j = R; 将基准数挖出形成第一个坑a[i]。
      2.j--由后向前找比它小的数,找到后挖出此数填前一个坑a[i]中。
      3.i++由前向后找比它大的数,找到后也挖出此数填到前一个坑a[j]中。
      4.再重复执行2,3二步,直到i==j,将基准数填入a[i]中
  • 代码实现:
#include <iostream>
using namespace std;
void quick_sort(int s[], int l, int r)
{
    if(l<r){
        int i = l,j = r,x = s[l];
        while(i<j){
            while(i<j&&s[j]>=x)
                j--;
            if(i<j)s[i++]=s[j];
            
            while(i<j&&s[i]<x)
                i++;
            if(i<j)s[j--]=s[i];
        }
        s[i] =x;
        quick_sort(s,l,i-1);
        quick_sort(s,i+1,r);
    }
}
int main() {
	int a[] = {9,1,2,5,7,4,8,6,3,5,0};
	int size = sizeof(a)/sizeof(int);
    quick_sort(a,0,size-1);
	for(int i =0;i<size;i++)
	cout<<a[i]<<" ";
	return 0;
}

归并排序

采用了分治策略 就是将原问题分解为一些规模较小的相似子问题,然后递归解决这些子问题,最后合并其结果作为原问题的解。

归并的核心思想 将两个有序的数组合并成一个大的有序的数组,通过递归把待排序数组变成完全有序数组。

归并的核心算法就是如何将2个数组合并

算法思想:
将待排序数组一直往下分解直到不可分解为止也就是一个数为一个子数组。然后对这些子数组层层合并(合并里有排序的过程)得到最后的有序数组。


public class Sort {
    public static void main(String[] args) {
        int[] a={10,4,6,3,8,2,5,7};
        //归并排序
        mergeSort(a,0,a.length-1);
    }
 
    private static void mergeSort(int[] a, int left, int right) {
        if(left<right){
            int middle = (left+right)/2;
            //对左边进行递归
            mergeSort(a, left, middle);
            //对右边进行递归
            mergeSort(a, middle+1, right);
            //合并
            merge(a,left,middle,right);
        }
    }
 
    private static void merge(int[] a, int left, int middle, int right) {
        int[] tmpArr = new int[a.length];
        int mid = middle+1; //右边的起始位置
        int tmp = left;
        int third = left;
        while(left<=middle && mid<=right){
            //从两个数组中选取较小的数放入中间数组
            if(a[left]<=a[mid]){
                tmpArr[third++] = a[left++];
            }else{
                tmpArr[third++] = a[mid++];
            }
        }
        //将剩余的部分放入中间数组
        while(left<=middle){
            tmpArr[third++] = a[left++];
        }
        while(mid<=right){
            tmpArr[third++] = a[mid++];
        }
        //将中间数组复制回原数组
        while(tmp<=right){
            a[tmp] = tmpArr[tmp++];
        }
    }
}

基数排序


  • 算法思想

     

     

    基数排序.gif
    参考:https://www.cnblogs.com/wuyudong/p/radix-sort-algorithm.html
    基数排序(Radix sort)是一种非比较型整数排序算法,其原理是将整数按位数切割成不同的数字,然后按每个位数分别比较。由于整数也可以表达字符串(比如名字或日期)和特定格式的浮点数,所以基数排序也不是只能使用于整数。
        它是这样实现的:将所有待比较数值(正整数)统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后, 数列就变成一个有序序列。

    1. 基数排序:通过序列中各个元素的值,对排序的N个元素进行若干趟的“分配”与“收集”来实现排序。
      分配:我们将L[i]中的元素取出,首先确定其个位上的数字,根据该数字分配到与之序号相同的桶中
      收集:当序列中所有的元素都分配到对应的桶中,再按照顺序依次将桶中的元素收集形成新的一个待排序列L[ ]
      对新形成的序列L[]重复执行分配和收集元素中的十位、百位...直到分配完该序列中的最高位,则排序结束
    2. 根据上述“基数排序”的展示,我们可以清楚的看到整个实现的过程
  • 代码实现
/************************基数排序****************************/
#include<stdio.h>
#include<stdlib.h>
int maxbit(int data[], int n) //辅助函数,求数据的最大位数
{
    int d = 1; //保存最大的位数
    int p = 10;
    for(int i = 0; i < n; ++i) {
        while(data[i] >= p) {
            p *= 10;
            ++d;
        }
    }
    return d;
}
void radixsort(int data[], int n)   //基数排序
{
    int d = maxbit(data, n);   //数组中的元素的最大位数
    int *tmp = (int *)malloc(n * sizeof(int));
    int *count = (int *)malloc(10 * sizeof(int));   //计数器
    int i, j, k;
    int radix = 1;
    for(i = 1; i <= d; i++) {   //进行d次排序
        for(j = 0; j < 10; j++)
            count[j] = 0;    //每次分配前清空计数器
        for(j = 0; j < n; j++) {
            k = (data[j] / radix) % 10;   //计算每次循环某一位的数字
            count[k]++;    //统计每个桶中的记录数
        }
        for(j = 1; j < 10; j++)
            count[j] = count[j - 1] + count[j];   //第j个桶以及之前所有桶中元素的总数
        for(j = n - 1; j >= 0; j--) {    //将所有桶中记录依次收集到tmp中
            k = (data[j] / radix) % 10;
            tmp[count[k] - 1] = data[j];
            count[k]--;
        }
        for(j = 0; j < n; j++)   //将临时数组的内容复制到data中
            data[j] = tmp[j];
        radix = radix * 10;
    }
    free(tmp);
    free(count);
}
int main()
{
    int a[] = {288, 52, 123, 30, 212, 23, 10, 233};
    int n;
    n = sizeof(a) / sizeof(a[0]);
    radixsort(a, n);
    for(int k = 0; k < n; k++)
        printf("%d ", a[k]);
    printf("\n");
    return 0;
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值