• 内部排序算法


    概述

      计算机上的程序,往下说就是一堆门电路以及一些逻辑运算;往上说就几个主要的部件(cpu,寄存器,内存,硬盘)上数据的运算与流动。而数据结构和算法是计算机程序的存储结构和工作流程。恰当的数据结构和算法对特定项目有着意想不到的效率。其中排序算法是数据结构中比较重要的;因为几乎每种数据结构都有排序的操作。下面从排序算法的分类,各种排序算法的思想、具体的代码、时间复杂度、空间复杂度以及适用情况。排序算法又分为内部排序算法(指待排序的记录存放在计算机随机存储器中)和外部排序(指待排序的记录太大,以至于内存中一次不能容纳全部记录)

    排序算法的分类

      排序算法的分类有多种,主要看按什么来分类;

      按排序过程中依据不同的原则可以分为:插入排序,交换排序,选择排序,归并排序,分配排序。

      按工作量(时间复杂度)可以分为:简单排序算法(O(n^2)),先进排序算法(O(nlogn))和基数排序(O(d*n)).

    具体的算法

    (1) 插入排序

      插入排序有:直接插入排序,折半插入排序,2-路插入排序和希尔排序

      ①直接插入排序

        核心思想:假设当前有k个已经排好序的数据,现在有一个新的元素要插入到该k个有序数据中恰当的位置,从而使者k+1个元素有序。

        代码 

    1 void DirectInsertSort(int array[],int len) {
    2 //array 为需要进行排序的数组,而len为array数组的元素个数,注意第一个即下标为
    3 //0的位置上没有放元素。
    4   int i, j, len = len;
    5 for(i=2; i<=len; i++) {
    6 if(array[i] < array[i-1]) {
    7 array[0] = array[i];
    8 array[i] = array[i-1];
    9 for(j = i-2; array[0]<array[j]; --j) {
    10 array[j+1] = array[j];
    11 }
    12 array[j+1] = array[0];
    13 }
    14 }
    15
    16 }

        时间复杂度:O(n^2)    空间复杂度:O(n+3)

      ②折半插入排序

        核心思想:在直接插入排序过程中,对于第k+1个元素要插入到k个有序队列中,需要去查找恰当的位置,而这里的查找可以通过折半查找来替代,从而可以改进查找过程的速度。

        代码:

    1 void BinaryInsertSort(int array[],int length) {
    2 //第一个元素仍然当做哨兵
    3   int i,low,mid,high,len=length;
    4 low = 0;
    5 mid = 0;
    6 high = 0;
    7 for(i=2;i<=len;i++) {
    8 array[0] = array[i];
    9 low = 1;
    10 high = i-1;
    11 while(low <= high) {
    12 mid = (low + high)/2;
    13 if (array[0] < array[mid]) high = mid - 1;
    14 else low = mid + 1;
    15 }
    16 for(j=i-1; j>=high+1; j++) {
    17 array[j+1] = array[j];
    18 }
    19 array[high] = array[0];
    20 }
    21 }

    时间复杂度:O(n^2)     空间复杂度O(n+5)

    注意:折半插入排序算法并不能改进直接插入排序算法的时间复杂度和移动元素的个数,它只是能够减少比较的次数。故它的时间复杂度也是O(n^2).

    ③2-路插入排序算法

      核心思想:待排序的元素存放在array[]中,另外设置一个跟array相同类型大小的数组temp[],将array[1]赋值给temp[1],将temp[1]看成是排好序序列中间位置的元素,即现在有中间元素temp[1],比其小的有序表list1,比其大的有序表list2,这样从array的第二个元素开始同temp[1]比较,如果比其大,则插入到list2中,比其小则插入到list1中,在实际的实现算法中,将temp看成是一个循环向量,并设置两个指针first和final分别指示排序过程中得到的有序序列中的第一个记录和最后一个记录在temp中的位置。

    [参考http://blog.csdn.net/Sunboy_2050/archive/2010/06/03/5645831.aspx]

      代码

     

    1 void BinInsertSort(int array[],int len)
    2 {
    3 int* arr_d = (int *)malloc(sizeof(int) * len);
    4 int i,j,pos;
    5
    6 arr_d[0] = array[0];
    7 int first = 0,final = 0;
    8
    9 for (i = 1;i < len; i++ )
    10 {
    11 if (array[i] > arr_d[0])
    12 {
    13 //插入到更大的那一部分中
    14   for ( j= final;j>0;j--)
    15 {
    16 if (array[i] < arr_d[j])
    17 arr_d[j+1] = arr_d[j];
    18 else
    19 break;
    20 }
    21 arr_d[j+1] = array[i];
    22 final += 1;
    23 }
    24
    25 else
    26 {
    27 //插入到更小的那一部分中
    28   if (first ==0)
    29 {
    30 arr_d[len-1] = array[i];
    31 first =len-1;
    32 }
    33 else
    34 {
    35
    36 for (j = first;j <= len-1;j++)
    37 {
    38 if (array[i] > arr_d[j])
    39 arr_d[j-1] = arr_d[j];
    40 else
    41 break;
    42 }
    43 arr_d[j-1] = array[i];
    44 first -= 1;
    45 }
    46 }
    47
    48 }
    49 for (i = 0;i < len; i++)
    50 {
    51 pos = (i + first)%len;
    52 array[i] = arr_d[pos];
    53 }
    54
    55 free(arr_d);
    56 }

    图解:

    时间复杂度 O(n^2)  空间复杂度 O(2n+5)

    ④改进的2-路插入排序算法 [参看http://epub.cnki.net/grid2008/detail.aspx?filename=HDZR200402015&dbname=CJFD2004]  

    在原来的数组上实现了2-路插入排序算法。时间复杂度 O(n^2)  空间复杂度 O(n+5)

    ⑤希尔排序

      核心思想:先将整个待排记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录“基本有序时”,再对全体记录进行一次直接插入排序。该算法依靠一个增量序列,通过该增量序列中的每个值,对记录进行分割并直接插入排序,通常这些增量值都是一序列的素数。

    [注意]:这里的增量序列的最后一个元素必须是1,因为最后要进行一次整体的插入排序。

      代码:

     

    1 void ShellInsert(int array[],int dk,int len) {
    2 //len 是array数组的长度
    3   int i=0,j;
    4 for(i=dk+1; i<=len;i++) {
    5 if(array[i] < array[i-dk]) {
    6 array[0] = array[i];
    7 for(j=i-dk; j>0&&(array[0]<array[j]);j-=dk)
    8 array[j+dk] = array[j];
    9 array[j+dk] = array[0];
    10 }
    11 }
    12 }
    13  void ShellSort(int array[],int dlta[],int len) {
    14 //len是dlta数组的长度
    15   int i=0,length=sizeof(array)/sizeof(int);
    16 for(i=0; i<len, i++) {
    17 ShellInsert(array,dlta[i],length);
    18 }
    19 }

    时间复杂度:O(n^1.5)           空间复杂度O(n+2)

    (2)交换排序算法

      交换排序算法有:冒泡排序算法和快速排序算法

    ①冒泡排序算法

      核心思想:每一趟都是将一个元素沉底,第一趟是最大的元素,最后一趟是倒数第二小的元素,每次都像一个气泡沉底,所以叫冒泡排序算法。具体的一趟是拿到当前无序元素中最大的元素。

      代码

     

    1 void BubbleSort(int array[],int len) {
    2 //第一个元素不当哨兵
    3   int i,j,k,temp;
    4 for(i=0; i<len; i++) {
    5 k = len-i-1;
    6 for(j=0; j<k;j++) {
    7 if(array[j]> array[j+1]) {
    8 temp = array[j+1];
    9 array[j+1] = array[j];
    10 arrau[j] = temp;
    11 }
    12 }
    13 }
    14 }

      时间复杂度  O(n^2)   空间复杂度  O(n+4)

    ②快速排序算法

      核心思想:通过一趟排序算法将待排记录分割成两部分,其中一部分记录的关键字均比另一部分记录的关键字小,然后再对这两部分队列进行这样的排序,从而分割成四部分,一直这样下去,知道每一个部分都有序。具体做法:以第1个元素作为枢轴,指针low指向当前枢轴的位置,将枢轴位置上的数据拿出来,指针high指向最后一个元素,从high指针开始往前走,当遇到比枢轴小的元素时,停下来,将该更小的元素放入low所指的位置,然后low指针往后走,遇到比枢轴大的元素时,停下来,将该更大的元素放入high所指的位置;接着重复这两个动作,从high开始。一直到(low>=high).

      代码

     

    1 int Partition(int array[],int low,int high) {
    2 //每一趟都确定一个元素的位置,并返回该位置
    3 //array[0] 作为哨兵
    4   array[0] = array[low];
    5 while(low < high) {
    6 //从high下标开始找比枢轴小的元素,找到就跳出第一个while,将该元素存放到low所指向的位置
    7   while(low < high && array[high] >= array[0]) --high;
    8 array[low] = array[high];
    9 //从low下标开始往后找比枢轴大的元素,找到就跳出第二个while,将该元素存放到high所指向的位置
    10   while(low < high && array[low] <= array[0]) ++low;
    11 array[high] = array[low];
    12 }
    13 array[low] = array[0];
    14 return low;
    15 }
    16
    17  void QSort(int array[],int low,int high) {
    18 //一开始的时候low=0;high=n-1;
    19   int temp ;
    20 if(low < high) {
    21 temp = Partition(array,low,high);
    22 QSort(array,low,temp-1);
    23 QSort(array,temp+1,high);
    24 }
    25 }

    时间复杂度 O(nlogn)   空间复杂度(2n)

    (3)选择排序

      选择排序的基本思想是:每趟在n-i+1(i=1,2,…,n-1)个记录中选取关键字最小的记录作为有序序列中第i个记录。有简单选择排序算法,树形选择排序,堆排序。

     ①简单选择排序算法

      核心思想:通过n-i次关键字间的比较,从n-i+1个记录中选出关键字最小的记录,并和第i个记录交换。

      代码

     

    1 void SelectSort(int array[],int len) {
    2 int i,j,temp;
    3 for(i=0;i<len;i++) {
    4 temp = i;
    5 for(j=i+1;j<len;j++)
    6 if(array[j] < array[i]) temp = j;
    7
    8 if(temp != i) {
    9 array[i] = array[i]+ array[temp];
    10 array[temp] = array[i] - array[temp];
    11 array[i] = array[i] - array[temp];
    12 }
    13 }
    14 }

      时间复杂度 O(n^2)  空间复杂度  O(n+3)

    ②树形选择排序算法

      核心思想:首先对n个记录进行两两比较,然后在其中「n/2]个较小者之间再进行两两比较,如此重复,直至选出最小关键字的记录为止。

      时间复杂度 O(nlogn)  空间复杂度:很大,需要很多辅助存储空间。

    ③堆排序

      堆的定义:

        对于所有的元素都满足这样的条件:任何一个元素都比它的孩子结点小或者大,其中父亲节点小(大)的称为小(大)顶堆(严蔚敏的那本书上,认为父亲节点小的是大顶堆,我查了一些资料,是有一些资料这么说,但也有一些是不是这样的,一般称为最大堆的就是指父亲节点大,最小堆就是指父亲节点小。但我的理解是大顶堆就是指顶(父亲节点)是大的。所以下文都认为大顶堆是指父亲节点大的,小顶堆是指父亲节点元素小的)

      核心思想:假设现在已经将待排的n个记录建成了一个小顶堆,输出堆顶元素,此时,左子树和右子树都成为了一个小顶堆,将堆中最后一个(下标最大)元素,作为一个新的堆顶,然后从堆顶开始进行"筛选",用该元素和两个孩子比较,和更小者进行对换,一直这样直到它的孩子为空或它的孩子都比它大为止。现在还有一个问题,就是一开始的时候,那个堆是如何建立起来的。事实上,通过"筛选"操作就能够建立一个小顶堆,先将这n个记录构建成一颗二叉树,然后从该二叉树的第[n/2]个元素开始进行"筛选",直到下标为“0”,通过这样的“筛选”就能够建立起一个堆。

      代码:

     

    1 void HeapAdjust(int array[],int low,int high) {
    2 //array[low…high]中的元素除了array[low]之外均满足堆的定义,本函数调整array[low]的关
    3 //键字,使array[low…high]成为一个大顶堆
    4   int j,temp = array[low];
    5 for(j=2*low; j <= high; j*=2) {
    6 if( j < high && array[j] < array[j+1] ) ++j;
    7 if(temp >= array[j]) break;
    8 array[low] = array[j];
    9 low = j;
    10 }
    11 array[low] = temp;
    12 }
    13
    14  void HeapSort(int array[],int len) {
    15 //第一个元素当哨兵
    16   int i,temp;
    17
    18 for(i=len/2; i>0; i--)
    19 HeapAdjust(array,i,len);
    20 for(i=len; i>1; i--) {
    21 temp = array[1];
    22 array[1] = array[i];
    23 array[i] = temp;
    24 HeapAdjust(array,1,i-1);
    25 }
    26
    27 }

    时间复杂度 O(nlogn)  空间复杂度 O(n+2)

    (4)归并排序

      核心思想:归并的思想是将两个或两个以上的有序表组合成一个新的有序表。对n个待排记录划分为n个有序表,每个表中只有一个元素,然后两两表进行合并,从而合并为[n/2]个有序表,继续这样下去,直到所有表合并为一个有序表为止。

    1 #define NUM 256
    2  void Merge(int array[],int low,int mid,int high) {
    3 int j,k;
    4 int iStart=low, iEnd = high;
    5 int arr_d[NUM];
    6 for(j=mid+1,k=low; low<=mid && j <= high; ++k) {
    7 if(array[low,array[j])
    8 arr_d[k] = array[low++];
    9 else
    10 arr_d[k] = array[j++];
    11 }
    12 if(low < mid) {
    13 for(; k<=high; k++, low++)
    14 arr_d[k] = array[low++];
    15 }
    16
    17 if(j <= high) {
    18 for(; k<=high; k++,j++)
    19 arr_d[k] = array[j];
    20 }
    21
    22 for(j=iStart; j<=iEnd; j++)
    23 array[j] = arr_d[j];
    24 }
    25
    26
    27  void MSort(int array[],int low,int high) {
    28 int mid;
    29 if(low < high){
    30 mid = (low+high)/2;
    31 MSort(array,low,mid);
    32 MSort(array,mid+1,high);
    33 Merge(array,low,mid,high);
    34 }
    35 }
    36  void MergeSort(int array[],int len) {
    37 MSort(array, 1, len);
    38 }

    时间复杂度:  O(nlogn)  空间复杂度 O(1.5n)--最坏的时候

    (5) 分配排序

      分配排序有:桶排序和基数排序

      ①桶排序

        核心思想:把区间[0,1]划分成n个相同大小的子区间,每个区间叫做桶,然后将n个待排记录分布到各个桶上面去。划分时,各个桶已经有序,即桶值更小的,分布在其上的元素肯定小于分布在桶值更大的桶上的元素。而对于分布在同一个桶上面的元素,就要分别对他们进行排序,可以使用插入排序

      代码

    1 typedef struct BucketNode {
    2 int data;
    3 struct BucketNode * next;
    4 }BucketNode;
    5
    6 typedef struct Bucket {
    7 struct BucketNode *p;
    8 }Bucket;
    9  void InsertNode(struct Bucket* add_r, int index, int key) {
    10 struct BucketNode * p=NULL,*q=NULL,*temp=NULL;
    11 if(index == -1) {
    12 printf("error in the value of j");
    13 return;
    14 }
    15 temp = (BucketNode*)malloc(sizeof(BucketNode));
    16 if(!temp) {
    17 printf("2、error in malloc function");
    18 exit(1);
    19 }
    20 temp->data = key;
    21 temp->next = NULL;
    22
    23 if(NULL != add_r[index].p) {
    24 p = add_r[index].p;
    25 q = p;
    26 while(NULL != p && p->data < key) {
    27 q = p;
    28 p = p->next;
    29 }
    30
    31 if(NULL == P) {
    32 q->next = temp;
    33 }else {
    34 temp->next = q->next;
    35 q->next = temp;
    36 }
    37 }else {
    38 add_r[index].p = temp;
    39 }
    40 p = NULL;
    41 q = NULL;
    42 temp = NULL;
    43 free(p);
    44 free(q);
    45 free(temp);
    46 }
    47
    48  void BucketSort(int array[], int len) {
    49 //add_r作为数组array的桶,它的大小可以不用和array一样大,但是我们这里
    50 //设置成一样大. 假如array中最大的数时89,则num中存放10;最大数为889,num为100
    51   int i,j,max=array[0],num=1;
    52 struct Bucket *add_r;
    53 struct BucketNode * p;
    54 add_r = (int*)malloc( sizeof(Bucket) * len );
    55
    56 if(!add_r) {
    57 printf("1、error in malloc function!");
    58 exit(1);
    59 }
    60 for(i=0; i<len; i++)
    61 add_r[i].p = NULL;
    62 //找到最大的元素
    63   for(i=2; i<len; i++)
    64 if(max < array[i]) max = array[i];
    65 //设置num的值
    66   while(max) {
    67
    68 max = max / 10;
    69 num = num * 10;
    70 }
    71
    72 // 将array中的元素插入桶B中
    73   for(i=0; i<len; i++) {
    74 j = -1;
    75 j = array[i]/num;
    76 InsertNode(add_r, j, array[i]);
    77 }
    78 //将桶add_r中的元素进行合并
    79   j=0;
    80
    81 for(i=0; i<len; i++) {
    82 p = add_r[i].p;
    83 if(NULL != add_r[i].p && p != NULL) {
    84 p = add_r[i].p;
    85 array[j] = p->data;
    86 j++;
    87 p = p->next;
    88 }
    89 }
    90 }

    时间复杂度 O(n)  空间复杂度 O(4n)

    图例


    ②基数排序

        核心思想:基数排序跟前面的各种算法都不一样,因为它不需要进行关键字间的比较,只需要进行关键移动,借助多关键字排序对单逻辑关键字进行排序。

        时间复杂度O(n+rd)  d是指记录的进制,比如如果待排的记录为整数,则d为10;而r是指待排记录中最大的数所含有的关键字个数,比如待排最大记录为999,则认为r为3,因为它有三个关键字“个位、十位、百位”.

        空间复杂度O(n+rd)

    ③计数排序

      核心思想:假设待排记录都介于0与k之间的整数,此处k为某个整数,实际上我们找出待排记录中最大的元素就可以了。对每一个带排记录x,确定出小于x的元素个数,有了这一信息,就可以把x直接放到它在最终输出数组中的位置上了。

      代码

    1 void CountSort(int array[], int len) {
    2 //假设array中最大的元素
    3   int i,index;
    4 int *count = NULL,*add_r = NULL,max=array[0],min=array[0];
    5
    6 //找array[]数组中最大和最小的元素
    7   for(i=1; i<len; i++) {
    8 if(array[i] > max) max = array[i];
    9 else if(array[i] < min) min = array[i];
    10 }
    11 count = (int*)malloc(sizeof(int)*(max-min+1));
    12 if(NULL == count) {
    13 printf("1、error in malloc function!");
    14 exit(1);
    15 }
    16 add_r = (int*)malloc(sizeof(int)*len);
    17 if(NULL == add_r) {
    18 printf("2、error in malloc function!");
    19 exit(1);
    20 }
    21 for(i=0; i<len; i++) {
    22 index = array[i]-min;
    23 count[index] = count[index] +1;
    24 }
    25 for(i=1; i<(max-min+1); i++) {
    26 count[i] = count[i] + count[i-1];
    27 }
    28
    29 for(i=len-1; i>0; i--) {
    30 index = array[i]-min;
    31 add_r[count[index]] = array[i];
    32 count[index] = count[index]-1;
    33 }
    34 for(i=0; i<len; i++) {
    35 array[i] = add_r[i];
    36 }
    37 free(count);
    38 free(add_r);
    39 }
    时间复杂度:O(n) 空间复杂度 O(2n+k) k为array数组中最大记录与最小记录直接的差值
  • 相关阅读:
    白话机器学习的数学笔记系列1算法回归_一元回归+多项式回归
    使用verdaccio搭建npm私有库 pm2守护进程 nrm管理npm源
    Nginx笔记
    FileSystemResource 和 ClassPathResource 以及 ServletContextResource 获取资源用法
    ClickHouse 使用
    springboot整合nacos项目配置文件
    SpringBoot基础篇Bean之条件注入@ConditionalOnExpression
    java web项目服务端返回json结果时过滤字段为null的属性
    Navicat导出Excel格式表结构
    sql积累
  • 原文地址:https://www.cnblogs.com/Firefly727/p/2074343.html
Copyright © 2020-2023  润新知