数据结构中的排序算法

一、排序算法的性能比较:

《数据结构中的排序算法》
—–参考自《大话数据结构》

二、排序算法的具体实现:

2.1 冒泡排序

冒泡排序的基本思想是:两两比较相邻记录的关键字,如果反序则交换。

//注释:n是数组的长度,[0,i-1]之间已经排好序,第一个for循环是对当前的第i位排序,
//第二个for循环对[i,n-1]之间的相邻的数进行排序,相邻的逐一进行比较,使得小的逐渐向上交换
void BubbleSort(int arr[],int n)
{
    int i ,j ;
    for(i = 0;i<n;i++){
        for(j=n-2,j>=i;j--){
            if(arr[j]>arr[j+1]){
                swap(arr,j,j+1);
            }
        }
    }
}

改进的冒泡算法:
存在的问题是:若进行前几次的冒泡后,数据已经有序了,但是上面的方法仍然会对i进行后面的循环,同时也要对第二个for循环进行循环,可以设置状态量,当第二个for循环里面没有发生交换时,说明数组已经有序,不必再接着进行循环。

void BubbleSort(int arr[],int n)
{
    int i ,j ;
    bool status = true;
    for(i = 0;i<n && status;i++){
        status = false;   //循环一开始假设数组已经排好序
        for(j=n-2,j>=i;j--){
            if(arr[j]>arr[j+1]){
                swap(arr,j,j+1);
                status = true;  //当发生交换时,改变数组排好序的假设
            }
        }
    }
}

冒泡排序复杂度分析:
当待排序数组是逆序时,需要的比较的次数是一个累加和:(n-1)+(n-2)+…+1 = n(n-1)/2,时间复杂度为O(n的平方)。

2.2 简单选择排序

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

void selectionSort(int arr[], int n){

    for(int i = 0 ; i < n ; i ++){
        // 寻找[i, n)区间里的最小值
        int minIndex = i;   //假设当前最小值的索引为i,找到更小的再更换掉
        for( int j = i + 1 ; j < n ; j ++ )
            if( arr[j] < arr[minIndex] )
                minIndex = j;

        swap( arr[i] , arr[minIndex] );
    }

}

复杂度分析:
无论原始排序的好坏,最终的比较次数都是一样多,第i个for循环,需要进行n-i次关键关键字的比较,此时需要比较的次数是:(n-1)+(n-2)+…+1 = n(n-1)/2。而对于交换次数,最好时交换0次,最差时,需要(n-1)次.。

2.3 直接插入排序

直接插入排序时间一个记录插入到已经排好序的有序表中,从而得到一个新的、记录数增加1 的有序表。

void insertionSort(int arr[], int n){
    for( int i = 1 ; i < n ; i ++ ) {
        // 寻找元素arr[i]合适的插入位置 
        for( int j = i ; j > 0 ; j-- )
            if( arr[j] < arr[j-1] )
                swap( arr[j] , arr[j-1] );
           else
                break;
    }

    return;
}
void insertionSort(int arr[], int n){

    for( int i = 1 ; i < n ; i ++ ) {      
        int e = arr[i];
        int j; // j保存元素e应该插入的位置
        for (j = i; j > 0 && arr[j-1] > e; j--)
            arr[j] = arr[j-1];
        arr[j] = e;
    }

    return;
}

复杂度分析:根据记录的随机性,移动和交换的次数都是O(n^2)级别。

2.4希尔排序

上面提到的直接插入排序在一下两种情况下特别有用:1、序列基本有序;2、记录数特别少。
将相距某个“增量”的记录组成一个子序列,在这个子序列上进行直接插入排序,这个“增量”逐渐 递减,缩减到1,此时序列可以达到基本有序了。

void shellSort(int arr[], int n){

    int h = 1;
    while( h < n/3 )
        h = 3 * h + 1;
    // 计算 increment sequence: 1, 4, 13, 40, 121, 364, 1093...

    while( h >= 1 ){

        // 将数组变成h有序
        for( int i = h ; i < n ; i ++ ){

            // 对 arr[i], arr[i-h], arr[i-2*h], arr[i-3*h]... 使用插入排序
            int e = arr[i];
            int j;
            for( j = i ; j >= h && e < arr[j-h] ; j -= h )  //j因为要和前面的j-h进行比较,所以要保证j要大于h
                arr[j] = arr[j-h];
            arr[j] = e;
        }

        h /= 3;
    }
}

希尔排序最后一步(h=1)就是一个直接插入排序,选择一种的递增序列(第一个值为1),可以使得时间负责度为O(n^3/2),因为是跳跃式移动,所以不是稳定的排序。

2.5堆排序

堆是具有下列性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点值,称为大顶堆;或者每个结点的值都小于或等于其左右孩子的值,称为小顶堆。
堆排序主要的操作有两部分:初始构建堆和重建堆的反复筛选:
初始构建堆:

void HeapAdjust(int a[],int i,int n){
    int temp,j;
    temp = a[i];
    for(j = 2*i;j<=n-1;j=j*2){
        if(j<n&&a[j]<a[j+1])   ## j是左子树,j+1是右子树,比较左右子树较大的是哪个
            j++;
        if(temp>=a[j]){
            break;
        }   
        a[i] = a[j];
        i = j;
    }
    a[i]=temp;
}
void HeapSort(int a[],int n){
    int i;
    for (i=(n-1)/2;i>=0;i--){
        HeapAdjust(a,i,n);
    }
    for(i = n-1;i>0;i--){
        swap(a[0],a[i]);
        HeapAdjust(a,0,i-1);    ##将堆顶元素取出,交换第一个位置和最后的位置,将调整到最大堆的状态,
    }
}

堆排序的复杂度是O(nlogn),对原始数据的排序状态并不敏感,最好,最坏和平均都是O(nlogn),空间上只需要一个交换的暂存单元。记录是跳跃式进行,非稳定排序算法。

2.6归并排序

《数据结构中的排序算法》
归并的思想是:从这张图可以看出,是先将大的序列进行两两分割,再分割到只有每份只有1或是2时,就将两份进行两两合并。

void Merge(int SR[],int TR[],int i ,int m ,int n){
    int j,k,l;
    for(j = m+1,k=i;i<=m&&j<=n;k++){
        if(SR[i]<SR[j]){
            TR[k] = SR[i++];
        }
        else
            TR[k] = SR[j++];
    }
    if(i<=m){
        for(l = 0;l<=m-i;l++){
            TR[k+l] = SR[i+l];
        }
    }
    if(j<=n){
        for(l=0;l<=n-j;l++){
            TR[k+l] = SR[j+l];
        }
    }

}
void Msort(int SR[],int TR1[],int s,int t){
    int m;
    int TR2[10000];
    if(s==t)
        TR1[s] = SR[s];
    else{
        m = (s+t)/2;   ##取中点
        Msort(SR,TR2,s,m);  ##将SR中的s到m排好放到TR2中
        Msort(SR,TR2,m+1,t);  ##将SR中的m+1到t排好放到TR2中
        Merge(TR2,TR1,s,m,t);  ##将TR2中的s到t,中点为m放到TR1中
    }
}

归并排序最好、最坏和平均的时间性能的时间复杂度是O(nlogn),归并排序需要与原始记录同等数量的存储空间存放归并结果及递归深度为log2N的栈空间(堆排只是在原来的基础上进行排序,额外空间只需要1),因此空间复杂度是O(n+logn),归并排序是两两比较,稳定的排序算法。

非递归实现:

2.7快速排序:

void QuickSort(int a[],int low,int high)
{
        if(low >= high)
            return;

        int i = low;
        int j = high;
        int key = a[i];
        while( i < j )
        {
            //从右向左搜索,直到搜索到的数大于等于开始记录的数,交换其位置
            while(i<j && a[j] >= key)

                j--;
            if(i<j){
                a[i] = a[j];
            }      
            //从左向右搜索,直到搜索到的数小于等于开始记录的数,交换其位置
            while(i<j && a[i] <= key)
                i++;
            if(i<j)
                a[j] = a[i];
        }  


        a[i] = key;
        QuickSort(a,low,i-1);
        QuickSort(a,i+1,high);
}

堆排:
void HeadAdjust(int data[ ] ,int length,int k){
int tmp = data[k];
int i = 2*k+1;
while(i

    原文作者:排序算法
    原文地址: https://blog.csdn.net/u013015493/article/details/79313038
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞