数据结构之七大排序算法

排序有内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部的排序记录,在排序过程中需要访问外存

1.插入排序—直接插入排序(Straight Insertion Sort)
基本思想:
将一个记录插入到已排序好的有序表中,从而得到一个新,记录数增1的有序表。即:先将序列的第1个记录看成是一个有序的子序列,然后从第2个记录逐个进行插入,直至整个序列有序为止。

void insertSort(int a[], int n){
    int i, j;
    int temp;
    for (i = 1; i < n; i++){
        temp = a[i];
        for (j = i - 1; j >= 0; j--){
            if (temp < a[j])
                a[j + 1] = a[j];
            else
                break;
        }
        a[j+1] = temp;
    }
}

如果碰见一个和插入元素相等的,那么插入元素把想插入的元素放在相等元素的后面。所以,相等元素的前后顺序没有改变,从原无序序列出去的顺序就是排好序后的顺序,所以插入排序是稳定的。

插入排序—希尔排序(Shell`s Sort)
希尔排序是1959 年由D.L.Shell 提出来的,相对直接排序有较大的改进。希尔排序又叫缩小增量排序
基本思想:
先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录“基本有序”时,再对全体记录进行依次直接插入排序。
操作方法:
选择一个增量序列t1,t2,…,tk,其中ti>tj,tk=1;
按增量序列个数k,对序列进行k 趟排序;
每趟排序,根据对应的增量ti,将待排序列分割成若干长度为m 的子序列,分别对各子表进行直接插入排序。仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。

void shellSort(int a[], int n){
    int i, j;
    int temp;
    int dk;
    for (dk = n / 2; dk >= 1; dk = dk / 2){
        for ( i = dk; i < n; i +=dk){
            temp = a[i];
            for (j = i - dk; j >= 0; j -= dk){
                if (temp < a[j])
                    a[j + dk] = a[j];
                else
                    break;
            }
            a[j + dk] = temp;
        }
    }
}

3.选择排序—简单选择排序(Simple Selection Sort)
基本思想:
在要排序的一组数中,选出最小(或者最大)的一个数与第1个位置的数交换;然后在剩下的数当中再找最小(或者最大)的与第2个位置的数交换,依次类推,直到第n-1个元素(倒数第二个数)和第n个元素(最后一个数)比较为止。
操作方法:
第一趟,从n 个记录中找出关键码最小的记录与第一个记录交换;
第二趟,从第二个记录开始的n-1 个记录中再选出关键码最小的记录与第二个记录交换;
以此类推…..
第i 趟,则从第i 个记录开始的n-i+1 个记录中选出关键码最小的记录与第i 个记录交换,
直到整个序列按关键码有序。

void selectSort(int a[], int n){
    int flag;
    int temp;
    int j;
    for (int i = 0; i < n-1; i++){
        temp = a[i];
        flag = i;
        for (j = i + 1; j < n; j++){
            if (temp>a[j]){
                temp = a[j];
                flag = j;
            }
        }
        if (flag != j){
            a[flag] = a[i];
            a[i] = temp;
        }
    }
}

简单选择排序的改进——二元选择排序

简单选择排序,每趟循环只能确定一个元素排序后的定位。我们可以考虑改进为每趟循环确定两个元素(当前趟最大和最小记录)的位置,从而减少排序所需的循环次数。改进后对n个数据进行排序,最多只需进行[n/2]趟循环即可。具体实现如下:

void SelectSort(int r[],int n) {  
    int i ,j , min ,max, tmp;  
    for (i=1 ;i <= n/2;i++) {    
        // 做不超过n/2趟选择排序   
        min = i; max = i ; //分别记录最大和最小关键字记录位置  
        for (j= i+1; j<= n-i; j++) {  
            if (r[j] > r[max]) {   
                max = j ; continue ;   
            }    
            if (r[j]< r[min]) {   
                min = j ;   
            }     
      }    
      //该交换操作还可分情况讨论以提高效率  
      tmp = r[i-1]; r[i-1] = r[min]; r[min] = tmp;  
      tmp = r[n-i]; r[n-i] = r[max]; r[max] = tmp;   

    }   
}  

4.选择排序—堆排序(Heap Sort)
堆排序是一种树形选择排序,是对直接选择排序的有效改进。
基本思想:
由堆的定义可以看出,堆顶元素(即第一个元素)必为最小项(小顶堆)。
若以一维数组存储一个堆,则堆对应一棵完全二叉树,且所有非叶结点的值均不大于(或不小于)其子女的值,根结点(堆顶元素)的值是最小(或最大)的。如:
(a)大顶堆序列:(96, 83,27,38,11,09)
(b) 小顶堆序列:(12,36,24,85,47,30,53,91)
初始时把要排序的n个数的序列看作是一棵顺序存储的二叉树(一维数组存储二叉树),调整它们的存储序,使之成为一个堆,将堆顶元素输出,得到n 个元素中最小(或最大)的元素,这时堆的根节点的数最小(或者最大)。然后对前面(n-1)个元素重新调整使之成为堆,输出堆顶元素,得到n 个元素中次小(或次大)的元素。依此类推,直到只有两个节点的堆,并对它们作交换,最后得到有n个节点的有序序列。称这个过程为堆排序。

因此,实现堆排序需解决两个问题:
1. 如何将n 个待排序的数建成堆;
2. 输出堆顶元素后,怎样调整剩余n-1 个元素,使其成为一个新堆。

首先讨论第二个问题:输出堆顶元素后,对剩余n-1元素重新建成堆的调整过程。
调整小顶堆的方法:

1)设有m 个元素的堆,输出堆顶元素后,剩下m-1 个元素。将堆底元素送入堆顶((最后一个元素与堆顶进行交换),堆被破坏,其原因仅是根结点不满足堆的性质。

2)将根结点与左、右子树中较小元素的进行交换。

3)若与左子树交换:如果左子树堆被破坏,即左子树的根结点不满足堆的性质,则重复方法 (2).

4)若与右子树交换,如果右子树堆被破坏,即右子树的根结点不满足堆的性质。则重复方法 (2).

5)继续对不满足堆性质的子树进行上述交换操作,直到叶子结点,堆被建成。

再讨论对n 个元素初始建堆的过程。
建堆方法:对初始序列建堆的过程,就是一个反复进行筛选的过程。

1)n 个结点的完全二叉树,则最后一个结点是第个结点的子树。

2)筛选从第个结点为根的子树开始,该子树成为堆。

3)之后向前依次对各结点为根的子树进行筛选,使之成为堆,直到根结点。

算法的实现:

从算法描述来看,堆排序需要两个过程,一是建立堆,二是堆顶与堆的最后一个元素交换位置。所以堆排序有两个函数组成。一是建堆的渗透函数,二是反复调用渗透函数实现排序的函数。
//堆排序

void adjustHeap(int a[],int i,int n){
    int temp=a[i];
    int child=2*i+1;//左孩子位置
    while (child < n){
        if (child + 1 < n&&a[child] < a[child + 1])
            child++;
        if (a[i] < a[child]){
            a[i] = a[child];
            i= child;
            child = 2 * i + 1;
        }
        else break;
        a[i] = temp;
    }   
}
void buildHeap(int a[], int n){
    for (int i = n / 2 - 1; i >= 0; i--){
        adjustHeap(a, i, n);
    }

}
void heapSort(int a[], int n){
    buildHeap(a, n);
    for (int i = n - 1; i >= 0; i--){
        swap(a[i], a[0]);
        adjustHeap(a,0,i-1);
    }
}

分析:
设树深度为k,。从根到叶的筛选,元素比较次数至多2(k-1)次,交换记录至多k 次。所以,在建好堆后,排序过程中的筛选次数不超过下式:
而建堆时的比较次数不超过4n 次,因此堆排序最坏情况下,时间复杂度也为:O(nlogn )。
5.交换排序—冒泡排序(Bubble Sort)
基本思想:
在要排序的一组数中,对当前还未排好序的范围内的全部数,自上而下对相邻的两个数依次进行比较和调整,让较大的数往下沉,较小的往上冒。即:每当两相邻的数比较后发现它们的排序与排序要求相反时,就将它们互换。

//冒泡排序
void bubbleSort(int a[],int n){
    for (int i = 0; i < n - 1; i++){
        for (int j = n - 1; j>i; j--){
            if (a[j] < a[j - 1])
                swap(a[j], a[j - 1]);
        }
    }
}

冒泡排序算法的改进
对冒泡排序常见的改进方法是加入一标志性变量exchange,用于标志某一趟排序过程中是否有数据交换,如果进行某一趟排序时并没有进行数据交换,则说明数据已经按要求排列好,可立即结束排序,避免不必要的比较过程。本文再提供以下两种改进算法:

改进后算法如下:

void bubbleSort1(int a[], int n){
    bool flag = true;
    for (int i = 0; i < n - 1&&flag; i++){
        flag = false;
        for (int j = n - 1; j>i; j--){
            if (a[j] < a[j - 1]){
                swap(a[j], a[j - 1]);
                flag= true;
            }
        }
    }
}

传统冒泡排序中每一趟排序操作只能找到一个最大值或最小值,我们考虑利用在每趟排序中进行正向和反向两遍冒泡的方法一次可以得到两个最终值(最大者和最小者) , 从而使排序趟数几乎减少了一半。

改进后的算法实现为:

[cpp] view plain copy print?
void Bubble_2 ( int r[], int n){  
    int low = 0;   
    int high= n -1; //设置变量的初始值  
    int tmp,j;  
    while (low < high) {  
        for (j= low; j< high; ++j) //正向冒泡,找到最大者  
            if (r[j]> r[j+1]) {  
                tmp = r[j]; r[j]=r[j+1];r[j+1]=tmp;  
            }   
        --high;                 //修改high值, 前移一位  
        for ( j=high; j>low; --j) //反向冒泡,找到最小者  
            if (r[j]<r[j-1]) {  
                tmp = r[j]; r[j]=r[j-1];r[j-1]=tmp;  
            }  
        ++low;                  //修改low值,后移一位  
    }   
}   

6.交换排序—快速排序(Quick Sort)
基本思想:
1)选择一个基准元素,通常选择第一个元素或者最后一个元素,
2)通过一趟排序讲待排序的记录分割成独立的两部分,其中一部分记录的元素值均比基准元素值小。另一部分记录的 元素值比基准值大。
3)此时基准元素在其排好序后的正确位置
4)然后分别对这两部分记录用同样的方法继续进行排序,直到整个序列有序。

//快速排序
int partition(int a[], int low, int high){
    int pivotkey=a[low];
    while (low < high){
        while (low < high&&a[high] >= pivotkey)
            high--;
        swap(a[low],a[high]);
        while (low < high&&a[low] <= pivotkey)
            low++;
        swap(a[low], a[high]);
    }
    return low;
}
void Sort(int a[],int low,int high){
    int pivot;
    if (low < high){
        pivot = partition(a, low, high);
        Sort(a, low, pivot - 1);
        Sort(a,pivot+1,high);
    }

}
void quickSort(int a[],int n){
    Sort(a, 0, n - 1);
}

分析:
快速排序是通常被认为在同数量级(O(nlog2n))的排序方法中平均性能最好的。但若初始序列按关键码有序或基本有序时,快排序反而蜕化为冒泡排序。为改进之,通常以“三者取中法”来选取基准记录,即将排序区间的两个端点与中点三个记录关键码居中的调整为支点记录。快速排序是一个不稳定的排序方法。

7 归并排序(Merge Sort)

基本思想:
归并(Merge)排序法是将两个(或两个以上)有序表合并成一个新的有序表,即把待排序序列分为若干个子序列,每个子序列是有序的。然后再把有序子序列合并为整体有序序列。

//归并排序
void merge(int a[],int p,int q,int r){
    int i, j, k, n1, n2;
    n1 = q - p + 1;
    n2 = r - q;
    int *L = new int[n1];
    int *R = new int[n2];
    for (i = 0, k = p; i < n1;i++,k++)
        L[i] = a[k];
    for (i = 0, k = q + 1; i < n2; i++, k++)
        R[i] = a[k];
    for (k = p, i = 0, j = 0; i < n1&&j < n2;k++){
        if (L[i]<R[j]){
            a[k] = L[i];
            i++;
        }
        else{
            a[k] = R[j];
            j++;
        }
    }
    if (i < n1){
        for (j = i; j < n1;j++,k++){
            a[k] = L[j];
        }
    }
    if (j< n2){
        for (i=j; i< n2; i++, k++){
            a[k] = R[i];
        }
    }
}
void mergeSort(int a[],int p,int r){
    if (p < r){
        int q = (p+r)/2;
        mergeSort(a,p,q);
        mergeSort(a,q+1,r);
        merge(a,p,q,r);
    }
}

int main(){
    int a[] = { 5, 9, 3, 45, 25, 36, 21 };
    //insertSort(a,7);

    //shellSort(a,7);

    //selectSort(a, 7);
    //SelectSort1(a, 7);

    //heapSort(a,7);

    //bubbleSort(a, 7); 
    //bubbleSort1(a,7);
    //bubbleSort2(a, 7);

    //quickSort(a,7);

    //mergeSort(a,0,6);

    for (int i = 0; i < 7; i++)
        cout << a[i] << " ";
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值