排序算法

冒泡排序 
基本思想:两两比较待排序的数,发现反序时交换,直到没有反序为止。
public static void BubbleSort(int[] R)
{
    for (int i = 0; i < R.Length - 1; i++)
    {
        bool noswap = true;
        for (int j = 0; j < R.Length - 1-i; j++)
        {
            if (R[j] > R[j + 1])
            {
                int temp = R[j];
                R[j] = R[j + 1];
                R[j + 1] = temp;
                noswap = false;
            }
        }
        if (noswap)
        {
            break;
        }
    }
}

快速排序 
基本思想:在待排序数列中任选出一个数作为基准,用这个基准将数列划分为左右两个子区,使得左子区的数都不大于基准数,而右子区的数都不小于基准数,称为完成第一次划分。如果左子区或右子区不为空,则对它进行同样的划分,直至为空为止。 
public static void QUICKSORT(int[] N,int left,int right)
{
    //数组元素如果不大于一个就无需排序。
    if (left < right)
    {
        int p = PARTITION(N, left, right);  //第一次划分
        QUICKSORT(N, left, p-1);    //递归处理左子区
        QUICKSORT(N, p+1, right);   //递归处理右子区
    }     
}
//划分
public static int PARTITION(int[] R, int left,int right)
{
    int i = left;
    int j = right;
    int temp = R[i];
    while (i != j)
    {
        //从左往右扫描,查找第一个比基准数小的数
        while ((R[j] >= temp) && (i<j))
        { 
            j--; 
        }
        if (i < j)                
        { 
            //交换找到的数和基准数,由于基准数还需交换多次,所以暂时不用将temp->R[j]
            R[i] = R[j];
            i++;
        }

        while ((R[i] <= temp) && (i < j))
        { 
            i++; 
        }
        if (i< j)
        { 
            R[j] = R[i];
            j--;
        }
    }
    //定位基准数
    R[i] = temp;
    return i;    
}

直接选择排序 
基本思想:每次从无序数组中选出一个最小的出来,放到已排好序的数组的最后。
public static void SELECTSORT(int[] R)
{   
    for (int i = 0; i < R.Length-1; i++)
    {
        int index = i;
        for (int j = i + 1; j < R.Length; j++)
        {
            if (R[j] < R[index])
            {
                index = j;
            }
        }
        //交换R[i]和R[index]
         if (index != i)
        {
            int t = R[i]; R[i] = R[index]; R[index] = t;
        }
    }    
}

插入排序 
基本思想:首先将数组的第一个数sortArray[0]看成是有序的,然后从第二个元素开始和它前面的元素进行比较,如果比前面的某一个数大,就交换。由于前面的元素是有序的,所以就使有序元素的个数逐渐增大,直到等于n。
public void Sort(int[] sortArray)
{
    int j = 0;
    int key = 0;   // key为哨兵 
    for (int i = 1; i < sortArray.Length; i++)     //[0..i-1]已经排好的有序列 
    {
        if (sortArray[i] < sortArray[i - 1])
        {
            key = sortArray[i];
            j = i - 1;
            while (j >= 0 && key < sortArray[j]) //当sortArray[i] ≥sortArray[j] 时终止 
            { 
                sortArray[j + 1] = sortArray[j];
                j--;
            }     
            sortArray[j + 1] = key;  //插入到j的后面 
        } 
    } 
}

希尔排序 
基本思想:通过一个逐渐减小的增量使一个数组逐渐趋近于有序从而达到排序的目的。
public void SortShell(int [] list)
 {
     int i;
     for(i=1;i<=list.Length/9;i=3*i+1);
     for(;i>0;i/=3)
     {
         for(int i=i+1;i<=list.Length;i+=inc)
        {
            int t=list[i-1];
            int j=i;
            while((j>inc)&&(list[j-inc-1]>t))
            {
                list[j-1]=list[j-inc-1];
                j-=inc;
            } 
            list[j-1]=t;
        } 
    } 
}

堆排序 堆排序快速排序归并排序一样都是时间复杂度为O(N*logN)的几种常见排序方法。学习堆排序前,先讲解下什么是数据结构中的二叉堆。

二叉堆的定义

二叉堆是完全二叉树或者是近似完全二叉树。

二叉堆满足二个特性:

1.父结点的键值总是大于或等于(小于或等于)任何一个子节点的键值。

2.每个结点的左子树和右子树都是一个二叉堆(都是最大堆或最小堆)。

当父结点的键值总是大于或等于任何一个子节点的键值时为最大堆。当父结点的键值总是小于或等于任何一个子节点的键值时为最小堆。下图展示一个最小堆:

由于其它几种堆(二项式堆,斐波纳契堆等)用的较少,一般将二叉堆就简称为堆。

堆的存储

一般都用数组来表示堆,i结点的父结点下标就为(i – 1) / 2。它的左右子结点下标分别为2 * i + 1和2 * i + 2。如第0个结点左右子结点下标分别为1和2。

堆的操作——插入删除

下面先给出《数据结构C++语言描述》中最小堆的建立插入删除的图解,再给出本人的实现代码,最好是先看明白图后再去看代码。

堆的插入

每次插入都是将新数据放在数组最后。可以发现从这个新数据的父结点到根结点必然为一个有序的数列,现在的任务是将这个新数据插入到这个有序数据中——这就类似于直接插入排序中将一个数据并入到有序区间中,对照《白话经典算法系列之二 直接插入排序的三种实现》不难写出插入一个新数据时堆的调整代码:

  1. //  新加入i结点  其父结点为(i - 1) / 2  
  2. void MinHeapFixup(int a[], int i)  
  3. {  
  4.     int j, temp;  
  5.       
  6.     temp = a[i];  
  7.     j = (i - 1) / 2;      //父结点  
  8.     while (j >= 0 && i != 0)  
  9.     {  
  10.         if (a[j] <= temp)  
  11.             break;  
  12.           
  13.         a[i] = a[j];     //把较大的子结点往下移动,替换它的子结点  
  14.         i = j;  
  15.         j = (i - 1) / 2;  
  16.     }  
  17.     a[i] = temp;  
  18. }  

更简短的表达为:

  1. void MinHeapFixup(int a[], int i)  
  2. {  
  3.     for (int j = (i - 1) / 2; (j >= 0 && i != 0)&& a[i] > a[j]; i = j, j = (i - 1) / 2)  
  4.         Swap(a[i], a[j]);  
  5. }  

插入时:

  1. //在最小堆中加入新的数据nNum  
  2. void MinHeapAddNumber(int a[], int n, int nNum)  
  3. {  
  4.     a[n] = nNum;  
  5.     MinHeapFixup(a, n);  
  6. }  

堆的删除

按定义,堆中每次都只能删除第0个数据。为了便于重建堆,实际的操作是将最后一个数据的值赋给根结点,然后再从根结点开始进行一次从上向下的调整。调整时先在左右儿子结点中找最小的,如果父结点比这个最小的子结点还小说明不需要调整了,反之将父结点和它交换后再考虑后面的结点。相当于从根结点将一个数据的“下沉”过程。下面给出代码:

  1. //  从i节点开始调整,n为节点总数 从0开始计算 i节点的子节点为 2*i+1, 2*i+2  
  2. void MinHeapFixdown(int a[], int i, int n)  
  3. {  
  4.     int j, temp;  
  5.   
  6.     temp = a[i];  
  7.     j = 2 * i + 1;  
  8.     while (j < n)  
  9.     {  
  10.         if (j + 1 < n && a[j + 1] < a[j]) //在左右孩子中找最小的  
  11.             j++;  
  12.   
  13.         if (a[j] >= temp)  
  14.             break;  
  15.   
  16.         a[i] = a[j];     //把较小的子结点往上移动,替换它的父结点  
  17.         i = j;  
  18.         j = 2 * i + 1;  
  19.     }  
  20.     a[i] = temp;  
  21. }  
  22. //在最小堆中删除数  
  23. void MinHeapDeleteNumber(int a[], int n)  
  24. {  
  25.     Swap(a[0], a[n - 1]);  
  26.     MinHeapFixdown(a, 0, n - 1);  
  27. }  

堆化数组

有了堆的插入和删除后,再考虑下如何对一个数据进行堆化操作。要一个一个的从数组中取出数据来建立堆吧,不用!先看一个数组,如下图:

很明显,对叶子结点来说,可以认为它已经是一个合法的堆了即20,60, 65, 4, 49都分别是一个合法的堆。只要从A[4]=50开始向下调整就可以了。然后再取A[3]=30,A[2] = 17,A[1] = 12,A[0] = 9分别作一次向下调整操作就可以了。下图展示了这些步骤:

写出堆化数组的代码:

  1. //建立最小堆  
  2. void MakeMinHeap(int a[], int n)  
  3. {  
  4.     for (int i = n / 2 - 1; i >= 0; i--)  
  5.         MinHeapFixdown(a, i, n);  
  6. }  

至此,堆的操作就全部完成了(注1),再来看下如何用堆这种数据结构来进行排序。

堆排序

首先可以看到堆建好之后堆中第0个数据是堆中最小的数据。取出这个数据再执行下堆的删除操作。这样堆中第0个数据又是堆中最小的数据,重复上述步骤直至堆中只有一个数据时就直接取出这个数据。

由于堆也是用数组模拟的,故堆化数组后,第一次将A[0]与A[n - 1]交换,再对A[0…n-2]重新恢复堆。第二次将A[0]与A[n – 2]交换,再对A[0…n - 3]重新恢复堆,重复这样的操作直到A[0]与A[1]交换。由于每次都是将最小的数据并入到后面的有序区间,故操作完成后整个数组就有序了。有点类似于直接选择排序

  1. void MinheapsortTodescendarray(int a[], int n)  
  2. {  
  3.     for (int i = n - 1; i >= 1; i--)  
  4.     {  
  5.         Swap(a[i], a[0]);  
  6.         MinHeapFixdown(a, 0, i);  
  7.     }  
  8. }  

注意使用最小堆排序后是递减数组,要得到递增数组,可以使用最大堆。

由于每次重新恢复堆的时间复杂度为O(logN),共N - 1次重新恢复堆操作,再加上前面建立堆时N / 2次向下调整,每次调整时间复杂度也为O(logN)。二次操作时间相加还是O(N * logN)。故堆排序的时间复杂度为O(N * logN)。STL也实现了堆的相关函数,

两路归并排序

最差时间复杂度:O(nlogn) 平均时间复杂度:O(nlogn) 最差空间复杂度:O(n) 稳定性:稳定

两路归并排序(Merge Sort),也就是我们常说的归并排序,也叫合并排序。它是建立在归并操作上的一种有效的排序算法,归并操作即将两个已经排序的序列合并成一个序列的操作。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。 归并操作的基本步骤如下: 1.申请两个与已经排序序列相同大小的空间,并将两个序列拷贝其中; 2.设定最初位置分别为两个已经拷贝排序序列的起始位置,比较两个序列元素的大小,依次选择相对小的元素放到原始序列; 3.重复2直到某一拷贝序列全部放入原始序列,将另一个序列剩下的所有元素直接复制到原始序列尾。

设归并排序的当前区间是R[low..high],分治法的三个步骤是: 1.分解:将当前区间一分为二,即求分裂点 2.求解:递归地对两个子区间R[low..mid]和R[mid+1..high]进行归并排序; 3.组合:将已排序的两个子区间R[low..mid]和R[mid+1..high]归并为一个有序的区间R[low..high]。 递归的终结条件:子区间长度为1(一个记录自然有序)。

算法示意图:

代码实现:

  1. void Merge(int *a, int p, int q, int r)  
  2. {  
  3.     int n1 = q-p+1;  
  4.     int n2 = r-q;  
  5.     int *L = new int[n1+1];  
  6.     int *R = new int[n2+1];  
  7.     int i, j, k;  
  8.       
  9.     for (i=0; i<n1; i++){  
  10.         L[i] = a[p+i];  
  11.     }  
  12.     for (j=0; j<n2; j++){  
  13.         R[j] = a[q+j+1];  
  14.     }  
  15.     L[n1] = 10000000;  
  16.     R[n2] = 10000000;  
  17.   
  18.     for (i=0, j=0, k=p; k<=r; k++)  
  19.     {  
  20.         if (L[i]<=R[j])  
  21.         {  
  22.             a[k] = L[i];  
  23.             i++;  
  24.         }else{  
  25.             a[k] = R[j];  
  26.             j++;  
  27.         }  
  28.     }  
  29.   
  30.     delete []L;  
  31.     delete []R;  
  32. }  
  33.   
  34. void MergeSort1(int *a, int p, int r)  
  35. {  
  36.     if (p<r)  
  37.     {  
  38.         int q = (p+r)/2;  
  39.         MergeSort1(a, p, q);  
  40.         MergeSort1(a, q+1, r);  
  41.         Merge(a, p, q, r);  
  42.     }  
  43. }  
虽然插入排序的时间复杂度为O(n^2),归并排序的时间复杂度为O(nlogn),但插入排序中的常数因子使得它在n较小时,运行得要更快一些。因此,在归并排序算法中,当子问题足够小时,采用插入排序算法就比较合适了。

代码实现:

  1. void MergeSort2(int *a, int p, int r)  
  2. {  
  3.     if ((r-p)>=50) // 小于50个数据的数组进行插入排序  
  4.     {  
  5.         int q = (p+r)/2;  
  6.         MergeSort2(a, p, q);  
  7.         MergeSort2(a, q+1, r);  
  8.         Merge(a, p, q, r);  
  9.     }else  
  10.     {  
  11.         InsertionSort(a+p, r-p+1);  
  12.     }  
  13. }  

MergeSort1与MergeSort2算法排序时间实验结果比较:

数据量

1K

10K

100K

1000K

10000K

MergeSort1

0.001s

0.008s

0.065s

0.552s

5.875s

MergeSort2

<0.001s

0.001s

0.021s

0.219s

2.317s

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 适合毕业设计、课程设计作业。这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。 所有源码均经过严格测试,可以直接运行,可以放心下载使用。有任何使用问题欢迎随时与博主沟通,第一时间进行解答!

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值