常用的内排序算法的描述和实现

来源:互联网 发布:淘宝话费充值软件 编辑:程序博客网 时间:2024/06/01 20:52

基本概念:


排序的稳定性:假设ki=kj(1<=i<=n,1<=j<=n,i!=j),且在排序前的序列中ri领先于rj(即i<j)。如果排序后ri依然领先于rj,则称所用的排序算法是稳定的;反之,若可能使得排序后的序列中rj领先ri,则称所用的排序方法是不稳定的。

根据在排序过程中待排序的记录是否全部被放置在内存中,排序分为:内排序和外排序。
内排序是在排序整个过程中,待排序的所有记录全部被放置在内存中。外排序是由于排序的记录太多,不能同时放置在内存,整个排序过程需要在内外存之间多次交换数据才能进行。这里主要介绍的是内排序的多种方法。
从算法的简单性来看,我们将以下7种算法分为两类:
简单算法:冒泡、简单选择、直接插入。
改进算法:希尔、堆、归并、快速。

算法介绍:

交换两个整型数据的函数swap:

void swap(int *a,int *b){int temp = *a;*a = *b;*b = temp;}

1、冒泡排序

(1)冒泡排序是一种交换排序,它的基本思想是:两两比较相邻的关键字,如果反序则交换,直到没有反序的记录为止。

代码实现:
冒泡排序的初级版:交换排序

void swap_sort(int arr[],int len){int i,j;for (i=0;i<len-1;++i){for (j=i+1;j<len;++j){if (arr[i]>arr[j]){swap(&arr[i],&arr[j]);}}}}
冒泡排序的简单算法:
void bubble_sort(int arr[],int len){int i,j;for (i=0;i<len-1;++i){for (j=len-1;j>i;j--){if (arr[j]<arr[j-1]){swap(&arr[j],&arr[j-1]);}}}}
冒泡排序的升级版:
void bubble_sort_plus(int arr[],int len){int i,j;for (i=0;i<len-1;++i){int flag = true;for (j=len-1;j>i;j--){if (arr[j]<arr[j-1]){swap(&arr[j],&arr[j-1]);flag = false;}}if (flag){break;}}}
(2)交换排序的时间复杂度:O(n2)。

2、简单选择排序

(1)简单选择排序法就是通过n-i次关键字间的比较,从n-i+1个记录中选出关键字最小的记录,并和第i(1≤i≤n)个记录交换之。

代码实现:

void select_sort(int arr[],int len){int i,j;int min;for (i=0;i<len-1;++i){min = i;for (j=i+1;j<len;++j){if (arr[min]>arr[j]){min = j;}}if (min != i){swap(&arr[min],&arr[i]);}}}
(2)简单选择排序的时间复杂度:O(n2)。

3、直接插入排序:

(1)直接插入排序的基本操作是将一个记录插入到已经排好序的有序表中,从而得到一个新的、记录数增1的有序表。

代码实现:

void insert_sort(int arr[],int len){int i,j;for (i=1;i<len;++i){int temp = arr[i];if (arr[i]<arr[i-1]){for (j=i-1;j>=0 && arr[j]>temp;j--){arr[j+1] = arr[j];}arr[j+1] = temp;}}}
(2)直接插入排序的时间复杂度:O(n2)

3)适用条件:记录本身基本有序;记录数比较少

4、希尔排序

(1)所谓的基本有序,就是小的关键字基本在前面,大的基本在后面,不大不小的基本在中间,像{2,1,3,6,4,7,5,8,9}这样可以称为基本有序了。

代码实现:

void shell_sort(int arr[],int len){int increament = len;int i,j;do {increament = increament/3+1;for (i=increament;i<len;++i){int temp = arr[i];if (arr[i]<arr[i-increament]){for (j=i-increament;j>=0&&arr[j]>temp;j-=increament){arr[j+increament] = arr[j];}arr[j+increament] = temp;}}} while (increament>1);}
(2)分割排序记录的目的是减少待排序记录的个数,并使整个序列向基本有序发展。采取的分割的策略:将相距某个“增量”的记录组成一个子序列,这样才能保证在子序列内分别进行直接插入排序后得到的结果是基本有序而不是局部有序。

(3)增量序列的最后一个增量值必须为1。

5、堆排序

(1)堆是具有下列性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。

(2)堆排序:就是利用堆进行排序的方法。它的基本思想是,先将待排序的子序列构造成一个大顶堆。此时,整个序列的最大值就是堆顶的根结点。将它移走(其实就是将其与堆数组的末尾元素进行交换,此时末尾元素的值就是最大值),然后将剩下的n-1个序列重新构造成一个大顶堆,这样就能得到n个元素中的次小值。如此反复执行,便能得到一个有序序列了。(其实就是将大顶堆转换为小顶堆的过程)

(3)所谓的将待排序的序列构造成一个大顶堆,其实就是从下往上、从右到左,将每个非终端结点(非叶结点)当做根结点,将其和其子树调整成大顶堆。

代码实现:

void heap_sort(int arr[],int len){//先将数组元素调整成大顶堆int i;for(i=len/2-1;i>=0;i--)//i为非叶子结点的位置{heap_adjust(arr,i,len);//heap_adjust2(arr,i,len);}//交换最后的结点和根结点的值,并将剩余的元素重新调整为大顶堆for(i=len-1;i>0;i--){swap(&arr[0],&arr[i]);//交换根结点和最后一个结点heap_adjust(arr,0,i);//heap_adjust2(arr,0,i);//调整剩余元素为大顶堆}}void heap_adjust(int arr[],int k,int len){//将当前的结点当做根结点,判断其与子结点的大小,如果小于就进行交换int temp = arr[k];int j = 2*k+1;while(j < len){if (j+1<len && arr[j]<arr[j+1]){j += 1;}if (temp>=arr[j])//判断当前元素和子结点的大小{break;}//arr[k] = arr[j];//使用赋值调整swap(&arr[k],&arr[j]);//使用交换调整k = j;j = 2 * k + 1;}//arr[k] = temp;}void heap_adjust2(int arr[],int start,int end){    int temp = arr[start];//临时存储父结点    int i;    for (i=2*start+1;i<end;i=2*start+1)//i指向左孩子    {        //右孩子下标        if (2*start+2<end && arr[2*start+1] < arr[2*start+2])//左孩子小于右孩子        {            i++;//i指向右孩子        }        if (arr[i] > temp)//如果孩子大于双亲        {            arr[start] = arr[i];//将孩子赋值给双亲            start = i;//start指向孩子结点        }        else        {            break;        }    }    arr[start] = temp;//将父结点赋值给孩子结点}
(4)堆排序的时间复杂度:O(nlogn),堆排序是一种不稳定的排序方法。

6、归并排序算法

(1)归并排序就是利用归并的思想实现的排序方法。它的原理是假设初始序列含有n个记录,则可以看成是n个有序的子序列,每个子序列的长度是1,然后两两归并,得到┌n/2┐(┌x┐表示不小于x的最小整数)个长度为2或1的有序子序列;再两两归并,......,如此重复,直至得到一个长度为n的有序序列为止,这种排序方法称为2路归并排序。

代码实现:

使用递归实现:

//方法1void merge_sort(int arr[],int len){//   msort(arr,arr,0,len-1);    msort2(arr,0,len-1);}void msort(int arr[],int brr[],int s,int t){int m;int crr[20];if (s == t)//只有一个元素{brr[s] = arr[s];}else{m = (s+t)/2;//将arr[s...t]平分为arr[s...m]和arr[m+1...t]msort(arr,crr,s,m);//递归将arr[s...m]归并为有序的crr[s...m]msort(arr,crr,m+1,t);//递归将arr[m+1...t]归并为有序的crr[m+1...t]merge(crr,brr,s,m,t);//将crr[s...m]和crr[m+1...t]归并为brr[s...t]}}void merge(int arr[],int brr[],int i,int m,int n){    int j,k,s;    //将所有元素从小到大拷贝    for (j=m+1,k=i;i<=m && j<=n;k++)        //循环结束后,一定会结束前半个(或后半个)数组的复制,即i或j会超出范围    {        if (arr[i]<arr[j])        {            brr[k] = arr[i++];        }        else        {            brr[k] = arr[j++];        }    }    if(i<=m)//i没有超出    {        for(s=0;s<=m-i;s++)//将剩下元素arr[i...m]拷贝到brr        {            brr[k+s] = arr[i+s];        }    }    if (j<=n)//j没有超出    {        for(s=0;s<=n-j;s++)////将剩下元素arr[j...n]拷贝到brr        {            brr[k+s] = arr[j+s];        }    }}//方法2void msort2(int arr[],int start,int end){    if (start < end)    {        int mid = (start+end)/2;        msort2(arr,start,mid);        msort2(arr,mid+1,end);        merge2(arr,start,mid,end);    }}void merge2(int arr[],int start,int mid,int end){    int *brr = (int *)malloc(sizeof(int)*(end-start+1));    int low1,low2,high1,high2;    low1 = start;    low2 = mid+1;    high1 = mid;    high2 = end;    int i = 0;    while (low1 <= high1 && low2 <= high2)    {        if (arr[low1] < arr[low2])        {            brr[i++] = arr[low1++];        }        else        {            brr[i++] = arr[low2++];        }    }    while (low1 <= high1)    {        brr[i++] = arr[low1++];    }    while (low2 <= high2)    {        brr[i++] = arr[low2++];    }    int j = 0;    for (i=start;i<=end;i++)    {        arr[i] = brr[j++];    }    free(brr);}//非递归实现:void merge_sort2(int arr[],int len){    int k=1;    while (k<len)    {    //   merge_pass(arr,tr,k,len);    //   k = 2 * k;    //   merge_pass(tr,arr,k,len);        merge_pass2(arr,k,len);    //   merge_loop(arr,len,k);        k = 2 * k;    }}void merge_pass(int sr[],int tr[],int s,int n){    int i = 0;    int j;    while (i < n-2*s+1)//两两归并,s=1,2,4,...    {        merge(sr,tr,i,i+s-1,i+2*s-1);        i = i+2*s;    }    if (i<n-s)//归并最后两个子序列,刚开始i===n-s    {    //    printf("i===%d,s========%d\n",i,s);        merge(sr,tr,i,i+s-1,n-1);    }    else//若只剩下单个子序列,将其复制到新的数组中    {        for (j=i;j<n;j++)        {            tr[j] = sr[j];        }    }}void merge_pass2(int arr[],int s,int n){    int i = 0;    while (i < n-2*s+1)//两两归并    {        merge2(arr,i,i+s-1,i+2*s-1);        i = i+2*s;    }    if (i<n-s)//归并最后两个子序列    {        merge2(arr,i,i+s-1,n-1);    }}void merge_loop(int arr[],int len,int gap){    int *brr = (int *)malloc(sizeof(int) * len);    int low1 = 0;    int high1 = low1+gap-1;    int low2 = high1+1;    //当gap===8时    int high2 = low2+gap-1 < len ? low2+gap-1 : len-1;///////////////        int i = 0;    while(low2 < len)///////    {    //    printf("low2 ==== %d\n",low2);        while (low1 <= high1  &&  low2 <= high2)        {            if (arr[low1] < arr[low2])            {                brr[i++] = arr[low1++];            }            else            {                brr[i++] = arr[low2++];            }        }        while (low1 <= high1)        {            brr[i++] = arr[low1++];        }        while (low2 <= high2)        {            brr[i++] = arr[low2++];        }        low1 = high2+1;        high1 = low1+gap-1;        low2 = high1+1;        high2 = low2+gap-1 < len ? low2+gap-1:len-1;///////    }    printf("low1=====%d\n",low1);    while (low1 < len)    {        brr[i++] = arr[low1++];    }    for (int k=0;k<len;k++)    {        arr[k] = brr[k];    }    free(brr);}
(2)归并排序的时间复杂度:O(nlogn),空间复杂度:O(n+logn),归并排序是一种稳定的排序方法。

7、快速排序

(1)快速排序的基本思想是:通过一趟排序将待排记录分割成独立的两部分,其中一部分记录的关键字均比另一部分记录的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序的目的。

代码实现:

递归实现

void quick_sort(int arr[],int len){qsort(arr,0,len-1);}void qsort(int arr[],int low,int high){    int mid;    while (low < high)    {        mid = partition(arr,low,high);        qsort(arr,low,mid-1);    //    qsort(arr,mid+1,high);        low = mid+1;//优化,将递归深度降低    }}int partition(int arr[],int low,int high){int tmp = arr[low];while (low<high){while (low<high && arr[high]>=tmp){high--;}//swap(&arr[low],&arr[high]);arr[low] = arr[high];//优化,将交换改成赋值while (low<high && arr[low]<=tmp){low++;}//swap(&arr[low],&arr[high]);//优化,将交换改成赋值arr[high] = arr[low];}arr[low] = tmp;return low;}
(2)快速排序的时间复杂度:O(n2),空间复杂度:O(logn),快速排序是一种不稳定的排序方法。

(3)快速排序的优化:

优化选取枢轴

三数取中法:取三个关键字先进行排序,将中间数作为枢轴,一般是取左端、右端和中间三个数,也可以随机选取。

优化不必要的交换

优化递归操作

优化小数组时的排序方案

算法比较


从综合各项指标来说,经过优化的快速排序是性能最好的排序算法。

原创粉丝点击