数据结构之排序(C语言实现,插入,希尔,归并,快排)

排序
稳定与非稳定稳定性

1、稳定排序:如果 a 原本在 b 的前面,且 a == b,排序之后 a 仍然在 b 的前面,则为稳定排序。
2、非稳定排序:如果 a 原本在 b 的前面,且 a == b,排序之后 a 可能不在 b 的前面,则为非稳定排序。

插入排序
  • 算法实现:
    直接插入排序是一种最简单的插入排序。
    插入排序:每一趟将一个待排序的记录,按照其关键字的大小插入到有序队列的合适位置里,知道全部插入完成。从其他文章上看到一个很好的打牌的例子。假设你有6,5,3,7,4张牌,从5开始移动,第一趟移动一次,
    将5置于6前(56374),第二趟从3开始移动,移动两次移动到5之前(35674),第三趟7比6大不移动,第四趟4移动3次到5之前,好了连就凑好了。可以看出,n个数就需要排n-1趟。
    假设一组数中,有N个数,则插入排序由N-1趟排序组成,在P=1到P=N-1趟中,将位置P上的一个元素赋给tmp,在位置p之前所有比p位置大的元素向右移动,然后将P放到合适的位置上。
  • 时间复杂度
    插入排序平均情况为O(N2),最好情况为O(N),最坏情况O(N2);
  • 代码实现
#include<stdio.h>
#define MaxSize 50//可输入50个字符

void InsertionSort(int a[],int N) {
    int tmp=0, p, j=0;  
    /*p控制趟数,j控制移动的次数*/
    for (p = 1; p < N; p++) { 
    /*需要排N-1趟*/
        tmp = a[p];
        for (j = p; j > 0 && a[j - 1] > tmp; j--) {  
        /*控制移动的次数,只要前面的数大于位置p的数,就往右移*/
            a[j] = a[j - 1];
        }
        a[j] = tmp;/*跳出循环,合适的位置已找到,将位置p的值插入*/
    }
}

int main() {
    int a[MaxSize];
    int num,i;
    printf("scanf Digital number:");
    scanf_s("%d", &num);
    printf("scanf digital\n");
    for (i = 0; i < num; i++) {
        scanf_s("%d",&a[i]);
    }
    InsertionSort(a,num);
    printf("after ording\n");
   for (i = 0; i < num; i++) {
        printf("%d\t",a[i]);
    }
    return 0;
}

结果:
在这里插入图片描述

  • 时间复杂度
    由于嵌套循环,每趟花费n次迭代,直接插入排序时间复杂度直接插入排序的时间复杂度是O(N2)
    当数据正序时,执行效率最好,每次插入都不用移动前面的元素,时间复杂度为O(N)。 当数据反序时,执行效率最差,每次插入都要前面的元素后移,时间复杂度为O(N2)。所以最好情况下时间复杂度为O(N),最坏情况下为O(N2)。
希尔排序
  • 算法实现
    希尔(Shell)排序又称为缩小增量排序,它是一种插入排序。它是直接插入排序算法的一种威力加强版。该方法因DL.Shell于1959年提出而得名。希尔排序的基本思想是:把记录按增量(步长) 分组,对每组记录采用直接插入排序方法进行排序。 随着步长逐渐减小,所分成的组包含的记录越来越多,当步长的值减小到 1 时,整个数据合成为一组,构成一组有序记录,则完成排序。
    过程如图:
    在这里插入图片描述
    分析:
    第一步,gap=gap/n=5,以5为步长进行划分,{a[0]与a[5]},{a[1]与a[6}]},{a[2]a[7]},{a[3]与a[8]},{a[4]与a[9}],被划分为5组。每一对元素利用插入排序来进行比较。
    第二步,gap=gap/2=2,以步长为2划分。{a[0]与a[2],a[1]与a[3],a[2]与a[4],a[3]与a[5],a[4]与a[6],a[5]与a[7],a[7]与a[9]}
    第三步,gap为1,及直接比较两相邻元素即可。
  • 希尔排序的时间复杂度及稳定性
    希尔排序的时间复杂度与增量(即步长gap)的选取有关。
    使用希尔增量时希尔排序的最坏情形运行时间为θ(N2)。 使用Hibbard增量的希尔排序的最坏情形运行时间为θ(N3/2)。
    稳定性:
    希尔排序中相等数据可能会交换位置,所以希尔排序是不稳定的算法。
  • 希尔排序和插入排序对比
    稳定性:
    1、稳定排序:如果 a 原本在 b 的前面,且 a == b,排序之后 a 仍然在 b 的前面,则为稳定排序。
    2、非稳定排序:如果 a 原本在 b 的前面,且 a == b,排序之后 a 可能不在 b 的前面,则为非稳定排序。
    时间复杂度:插入排序平均情况为O(N2),最好情况为O(N),最坏情况O(N2);希尔排序(shell增量)平均情况为O(N1.3),最好情况为O(N),最坏情况O(N2);
    稳定性:希尔排序是不稳定的,插入排序算法是稳定的。
    空间复杂度:O(1)
    *代码实现
#include<stdio.h>

#define MaxSize 50 //可输入50个字符

/*希尔排序
*参数说明
*gap:步长(增量)
*/
void ShellSort(int a[],int N)
{
    int tmp=0, gap, i=0,j=0; 
    for (gap = N / 2; gap > 0; gap /= 2) {    //缩减增量   
        for (i = gap; i < N; i++) {
            tmp = a[i];
            for (j = i; j >= gap; j -= gap) {  //插入排序
                if (tmp < a[j - gap])
                    a[j] = a[j - gap];
                else
                    break;
            }
            a[j] = tmp;
        }
    }
}

int main() {
    int a[MaxSize];
    int num,i;
    printf("scanf Digital number:");
    scanf_s("%d", &num);
    printf("scanf digital\n");
    for (i = 0; i < num; i++) {
        scanf_s("%d",&a[i]);
    }
    ShellSort (a, num);
    printf("after ording\n");
   for (i = 0; i < num; i++) {
        printf("%d\t",a[i]);
    }
    return 0;
}

  • 结果
    在这里插入图片描述
归并排序
  • 算法实现
    简单来说,核心思想就是分治。
    分:就是将待排序的数不断的分为两半,直至数组中元素个数为1。
    治:将分好的各个元素排序,合并成成一个有序的序列。
    合:将合并好的序列在进行合并。
    如图所示:
    在这里插入图片描述动态演示过程:
    在这里插入图片描述
    合并过程:
    可以用两个数组,三个计数器实现。一个数组为要排序的原数组,一个为临时存放元素的数组。
    在这里插入图片描述
    如图所示,a[i]=a[j]数组中,选择较小的放入temp[k],原来的数组中谁的数据移走了,计数器就+1(也就是往后移比较数组中下一个元素),temp数组中有新的元素,计数器也+1, 依次类推,将原来数组中的元素通过比较放入临时数组中,最后将一个排序好的序列在复制到原数组中。
  • 归并排序中的递归(双递归)
    归并过程是将两个数组进行归并,所以我们会使用双递归。
    递归过程如图:
    在这里插入图片描述* 复杂度以及稳定性
    1、时间复杂度:O(nlogn)
    2、空间复杂度:O(n)
    3、稳定排序
  • 代码实现
#include<stdio.h>
#define MaxSize 50 //可输入50个字符
void Merge(int a[], int tmparray[], int left, int mid, int right);

/*递归实现归并排序
*参数说明
left:左边界
right:右边界
mid:中值
tmparray:临时数组
Merge:合并函数*/
void Msort(int a[],int tmparray[],int left,int right)
{
    int mid;
    if (left < right)
    {
        mid = (left + right) / 2;  //计算中值
        Msort(a,tmparray,left,mid); //从左边界到中值(左半部分分解为单个数组)
        Msort(a, tmparray, mid+1,right);  //从中值到右边界(右半部分分解为单个数组)
        Merge(a,tmparray,left,mid,right); //合并过程,先将的单个数组两两合并成左右两个有序序列,最后将序列合并
    }
}

void Merge(int a[], int tmparray[], int left, int mid, int right)
{
    int i = left;
    int e1 = mid;
    int j = mid + 1;
    int e2 = right;
    int k = left;
    while (i <= e1 && j <= e2)
    {
        if (a[i] < a[j])
            tmparray[k++] = a[i++];
        else
            tmparray[k++] = a[j++];

    }

    while (i <= mid)
    {
        tmparray[k++] = a[i++];
    }
    while (j <= left)
    {
        tmparray[k++] = a[j++];
    }
    k--;
    while (k >= left)
    {
        a[k] = tmparray[k];//将临时数组中的排序合并好的元素赋值到原来的数组中
        k--;
    }
}
int main() {
    int a[MaxSize];
    int tmparray[MaxSize];
    int num,i;
    printf("scanf Digital number:");
    scanf_s("%d", &num);
    printf("scanf digital\n");
    for (i = 0; i < num; i++) {
        scanf_s("%d",&a[i]);
    }
    Msort(a,tmparray,0,num-1);
    printf("after ording\n");
   for (i = 0; i < num; i++) {
        printf("%d\t",a[i]);
    }
    return 0;
}
  • 执行结果
    在这里插入图片描述##### 快速排序
  • 基本思想
    归并排序和快速排序
    为什么放在一起呢?原因是他们都用到了分治的思想。将一个大问题不断细分,最后解决。但为什么快速排序要比归并排序稍快?因为快速排序在分割的过程中是在适当的位置上进行,比归并排序分割更有效。
    举个例子,如图所示:
    在这里插入图片描述
    假设我们从中任意选取一个值为位枢纽元(什么是枢纽元稍后解释)。选取65为例。
    我们开始进行分割,比65小的放左边的框里,比65大的放右边的框里。这样我们就将这组数据分为两部分,对这两部分在分别做快速排序,最后排序完成。
  • 实现过程
    1.给定 一个数组S,如果数组中的元素个数为0或1,则返回。
    2.取S中任意元素v作为枢纽元。
    3.将S分为两个不相交的集合,s1属于{s-v,|x<v},s2属于{s-v,x>v}.
    4.分别对两组进行快速排序。先s1,然后v,在s2.
    主要对2和3过程进行进一步分析。
    枢纽元的选取
    假设我们一组数组,我们一般选取数组中的第一个元素作为枢纽元。
    但是这样做就会有一个弊端,如果输入是随机的,那么这是可以接受的,但是如果输入是预排序的或是反序的,那么这样的枢纽元就产生一个劣质的分割,因为所有的元素不是都被划入S;就是都被划人S2。更有甚者,这种情况可能发生在所有的递归调用中。实际上,如果第一个元素用作枢纽元而且输入是预先排序的,那么快速排序花费的时间将是二次的,可是实际上却根本没干什么事,这是相当尴尬的。(实际上就变成了了冒泡排序,时间复杂度就变为了o(n^2)).
    一种安全的方针是随机选取枢纽元。一般来说这种策略非常安全,除非随机数生成器有问题(它不像你可能想像的那么罕见),因为随机的枢纽元不可能总在接连不断地产生劣质的分割。另一方面,随机数的生成一般是昂贵的,根本减少不了算法其余部分的平均运行时间。
    三数中值分割法(Median-of-Three Partitioning)
    事实上,随机性并没有多大的帮助,因此一般的
    做法是使用左端、右端和中心位置上的三个元素的中值作为枢纽元。例如,输人为8,1,4,
    9, 6, 3, 5, 2, 7, 0, 它的左边元素是8, 右边元素是0, 中心位置(L(Left+Right) 2」) 上的
    元素是6。于是枢纽元则是u=6.显然使用三数中值分割法消除了预排序输人的坏情形(在这种情形下,这些分割都是一样的),并且减少了快速排序大约5%的运行时间。
    分割的实现
    第一种情况是选取数组中第一个元素做枢纽元。
    选取第一个元素,作为base值,两个分别指向数组首尾的元素left和right,先从右往左进行进行扫描比base值小的数,找到后将指向的数与left所指向的数交换。然后left从左往右,找比base值大的数,找到后与right的值进行交换。right与left交替扫描,直到相遇,停止。如图所示
    图片来源:https://www.cnblogs.com/jingmoxukong/p/4302891.html
    在这里插入图片描述
    改进
    第二种就是利用中值分割法进行分割。
    输人为8,1,4,9, 6, 3, 5, 2, 7, 0, 它的左边元素是8, 右边元素是0, 中心位置(L(Left+Right) 2」=4,即数组下标为4的元素) 上的元素是6。于是枢纽元则是u=6。然后让枢纽元与最后一个元素交换,将枢纽元放到最后的位置,得到的数组顺序就如下图所示:
    在这里插入图片描述
    接下来令i,j分别指向数组中第一个元素和倒数第二个元素。此时令j从右往左开始扫描,找到比base(6)小的元素,找到后与i所指的元素交换。
    在这里插入图片描述
    扫描到2,得到
    在这里插入图片描述
    向右扫描后,j不动,i从左往右扫描,扫描比6大的数,然后再与j所指元素交换。不断执行此过程,i与j交错,
    不再交换,最后把i所指的元素与枢纽元交换,得到
    在这里插入图片描述
    分割完成。
  • 时间复杂度和稳定性
    稳定性:由于涉及到元素交换,所以为非稳定排序
    时间复杂度:快排的时间复杂度极大程度取决于枢纽元的选取,若以数组第一个元素为枢纽元,则最坏情况下为O(N^2),一般情况下为O(NlgN.若随机选取枢纽元或者采用三元中值分割法,则时间复杂度为O(NlgN).
  • 代码实现
    1.以第一个数组元素做枢纽元

#include<stdio.h>
#define MaxSize 50 //可输入50个字符

/*快速排序(以第一个元素为枢纽元)
*left:数组第一个元素
*right数组最后一个元素
*/
void Quick_Sort(int arr[], int left, int right)
{
	if (left >= right) {
		return;        //数组中只有一个元素活不符合实际情况,直接返回
	}
	int key = arr[left];  //以第一个元素作为枢纽元
	int begin = left;
	int end = right;
	while (begin != end) {  //只要beign与and不相遇就一直进行扫描过程
		while (begin < end && arr[end] >= key) { //从右向左扫描,直到扫描到小于key的数,循环终止(或相遇)
			end--;
		}
		if (end > begin) {
			arr[begin] = arr[end];  //end所指向的数赋给begin所指向的数
		}
		while (begin < end && arr[begin] <= key) { //接下来从左向右扫描,直到扫描到大于于key的数,循环终止(或相遇)
			begin++;
		}
		if (begin < end) {
			arr[end] = arr[begin];
		}
	}
	arr[begin] = key;//begin最后所指的元素作为枢纽元,即左半部分是比key小的元素,右半部分是比key大的元素
	/*分割完成后分别递归的对左右两部分继续执行快速排序*/
	Quick_Sort(arr, left, begin - 1);//对左半部分排序
	Quick_Sort(arr, begin + 1, right);//对右半部分排序
}

int main() {
    int a[MaxSize];
    int num,i;
    printf("scanf Digital number:");
    scanf_s("%d", &num);
    printf("scanf digital\n");
    for (i = 0; i < num; i++) {
        scanf_s("%d",&a[i]);
    }
	Quick_Sort(a,0,num-1);
    printf("after ording\n");
   for (i = 0; i < num; i++) {
        printf("%d\t",a[i]);
    }
    return 0;
}

结果:
在这里插入图片描述

2.利用中值分割法进行分割

#include<stdio.h>
#define MaxSize 50 //可输入50个字符
#define Cutoff 3

/*互换函数*/
void Swap(int* num1, int* num2)
{
    int temp = *num1;
    * num1 = *num2;
    * num2 = temp;
}

/*插入排序函数*/
void insertSort(int A[], int N)
{
    /*优化后的插入排序*/
    int j = 0;
    int p = 0;
    int temp = 0;
    for (p = 1; p < N; p++)
    {
        temp = A[p];
        for (j = p; j > 0 && A[j - 1] > temp; j--)
        {
            A[j] = A[j - 1];
        }
        A[j] = temp;
    }

}

/*三元中值分割法*/
int median(int* arry, int left, int right)
{
     int temp =  (left+right)/2;
     if (arry[left] > arry[temp])
        Swap(&arry[left],&arry[temp]);
     if (arry[left] > arry[right])
        Swap(&arry[left], &arry[right]);
     if (arry[temp] > arry[right])
        Swap(&arry[temp], &arry[right]);
     Swap(&arry[temp], &arry[right-1]);//将中值与最后一个元素的位置互换,即枢纽元放到最后
     return arry[right - 1]; //返回枢纽元
 }

/*快排*/
void Quick_Sort(int arry[], int left, int right)
{
      int i, j, pivot;
      if (left + Cutoff <= right)  //检查数组长度是否大于3
      {
         i = left; j = right - 1; 
         pivot = median(arry, left, right);
          while (1)
          {
             while (arry[--j] > pivot) {} //从右往左扫描
             while (arry[++i] < pivot) {}//从左往右扫描
             if (i < j)
                Swap(&arry[i],&arry[j]);
             else
                   break; //相遇循环结束
          }
             Swap(&arry[i],& arry[right-1]);//将分界点放到中间,即将最后i指向的元素与枢纽元进行交换
             Quick_Sort(arry, left, i - 1);//枢纽元左半部分进行快速排序
             Quick_Sort(arry, i + 1, right);//枢纽元右半部分进行快速排序
      }
      else //如果数组长度小于3,直接用插入排序,节省时间
      {
         insertSort(arry+left, right - left + 1);
      }
}

/*主函数*/
int main() {
    int a[MaxSize];
    int num,i;
    printf("scanf Digital number:");
    scanf_s("%d", &num);
    printf("scanf digital\n");
    for (i = 0; i < num; i++) {
        scanf_s("%d",&a[i]);
    }
	Quick_Sort(a,0,num-1);
    printf("after ording\n");
   for (i = 0; i < num; i++) {
        printf("%d\t",a[i]);
    }
    return 0;
}

结果:
在这里插入图片描述

部分引用和图片来源
https://blog.csdn.net/daigualu/article/details/78399168
https://zhuanlan.zhihu.com/p/57088609

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值