王道C语言笔记NOTE-中级阶段Note7-排序算法

一、排序

1、分类:排序算法分为交换类排序,插入排序,选择排序,归并排序

2、交换排序

①、冒泡排序

②、快速排序

二、冒泡排序

1、基本思想:从后往前(或从前 往后)两两比较相邻元素的值,(若A[j-1]>A[j]) ,则交换它们,直到序列比较完。称为第一趟冒泡,结果是将最小的元素交换到待排序列的第一个位置。关键字最小的元素如气泡一般逐渐往上“漂浮”直至“水面”。下一趟冒泡时,前一趟确定的最小元素不再参与比较,每趟冒泡的结果是把序列中的最小元素放到了序列的最终位置....这样最多做n-1趟冒泡就能把所有元素排好序。

2、冒泡过程图示

 

 

 

3、代码实战

①、步骤:首先通过随机数生成10个元素,多次测试排序算法是否正确。先打印随机生成后的元素,后通过冒泡排序对元素进行排序,再次打印排序后的元素顺序。

②、代码

 

复制代码
 1 #include<stdio.h>
 2 #include<stdlib.h>
 3 #include<time.h>
 4 //顺序表数据结构定义
 5 typedef struct{
 6     int *elem;//存储元素的起始地址
 7     int len;//元素个数
 8 }SSTable;
 9 
10 //初始化顺序表
11 void Init_ST(SSTable &ST,int len)
12 {
13     ST.elem=(int*)malloc(sizeof(int)*len);
14     ST.len=len;
15     //随机数生成
16     srand(time(NULL));
17     int i;
18     for(i=0;i<ST.len;i++)
19     {
20         ST.elem[i]=rand()%100;//生成的随机数是0-99之间
21     }
22 }
23 
24 //打印数组中的元素
25 void Print_ST(SSTable ST)
26 {
27     int i=0;
28     for(i=0;i<ST.len;i++)
29     {
30         printf("%3d",ST.elem[i]);
31     }
32     printf("\n");
33 }
34 
35 //交换两个元素
36 void swap(int &a,int &b)
37 {
38     int tmp=a;
39     a=b;
40     b=tmp;
41 }
42 
43 //冒泡排序
44 void Bubble_Sort(int *A,int len)
45 {
46     int i,j;
47     for(i=0;i<len-1;i++)
48     {
49         for(j=len-1;j>i;j--)
50         {
51             if(A[j]<A[j-1])
52             {
53                 swap(A[j],A[j-1]);
54             }
55         }
56     }
57 }
58 
59 
60 //主函数
61 int main()
62 {
63     SSTable ST;
64     Init_ST(ST,10);
65     Print_ST(ST);//打印排序前的顺序表
66     Bubble_Sort(ST.elem,10);
67     printf("冒泡排序后的结果是:\n");
68     Print_ST(ST);
69     return 0;
70 }
复制代码

 

③、结果

 

4、复杂度分析

①、时间复杂度是O(n2):内外层循环相乘。

②、空间复杂度是O(1):未使用额外空间。

三、快速排序

1、基本思想:假设是按从小到大排序,先找到数组中的一个分割值,把比分割值小的数都放在数组左边,把比分割值大的数都放在数组右边,这样分割值的位置就确定了。数组一分为二,只需要排前一半数组和后一半数组,复杂度直接减半。采用这种思想,不断的进行递归,最终分割得只剩下一个元素时,整个序列就自然是有序的。

2、代码实战

①、代码

 

复制代码
 1 #include<stdio.h>
 2 #include<stdlib.h>
 3 #include<time.h>
 4 //顺序表数据结构定义
 5 typedef struct{
 6     int *elem;//存储元素的起始地址
 7     int len;//元素个数
 8 }SSTable;
 9 
10 //初始化顺序表
11 void Init_ST(SSTable &ST,int len)
12 {
13     ST.elem=(int*)malloc(sizeof(int)*len);
14     ST.len=len;
15     //随机数生成
16      srand(time(NULL));
17      int i;
18      for(i=0;i<ST.len;i++)
19      {
20           ST.elem[i]=rand()%100;//生成的随机数是0-99之间
21      }
22 }
23 
24 //打印数组中的元素
25 void Print_ST(SSTable ST)
26 {
27      int i=0;
28      for(i=0;i<ST.len;i++)
29      {
30       printf("%3d",ST.elem[i]);
31      }
32      printf("\n");
33 }
34 
35 //交换两个元素
36 void swap(int &a,int &b)
37 {
38      int tmp=a;
39      a=b;
40      b=tmp;
41 }
42 
43 //分割
44 int Partition(int *A,int low,int high)
45 {
46     int pivot=A[low];//首先使用左边变量存储值
47     while(low<high)
48     {
49         while(low<high&&A[high]>=pivot)
50         {
51             high--;
52         }
53         A[low]=A[high];
54         while(low<high&&A[low]<=pivot)
55         {
56             low++;
57         }
58         A[high]=A[low];
59     }
60     A[low]=pivot;
61     return low;   
62 }
63 
64 
65 //递归实现
66 void Quick_Sort(int *A,int low,int high)
67 {
68     if(low<high)
69     {
70         int pivotpos=Partition(A,low,high);
71         Quick_Sort(A,low,pivotpos-1);
72         Quick_Sort(A,pivotpos+1,high);
73     }
74 }
75 
76 //主函数
77 int main()
78 {
79      SSTable ST;
80      Init_ST(ST,10);
81      Print_ST(ST);//打印排序前的顺序表
82      printf("快速排序后的结果是:\n");
83     Quick_Sort(ST.elem,0,9);//快速排序
84     Print_ST(ST);//打印排序后的顺序表
85     return 0;
86 }
复制代码

 

②、结果

 

3、复杂度分析

①、分析:每次快速排序数组被平均的一分为二,Quick_Sort递归的次数是log2n,第一次partition遍历次数是n,分成两个数组后,每个数组遍历n/2次,加起来还是n,因此时间复杂度是O(nlog2n);如果数组本身是从大到小有序时,每次仍然采用最左边的数作为分割值,那么每次数组都不会二分,导致递归n次,最坏时间复杂度是O(n2)。

解决方法:首先随机选择一个下标,先将对应下标的值和最左边的元素进行交换,在进行分割操作,从而降低出现最坏时间复杂度的概率,但是并不能完全避免。

②、结论

a.时间复杂度

最好时间复杂度:O(nlog2n)
平均时间复杂度:O(nlog2n)
最坏时间复杂度:O(n2)

b.空间复杂度:O(log2n)

因为递归次数是log2n,而每次递归的形参都是需要占用空间的。

四、插入排序

插入排序分类

①、直接插入排序

②、折半插入排序

③、希尔排序

五、直接插入

1、直接插入排序原理:如果一个序列只有一个数,那么该序列是自然有序的。插入排序首先将第一个数视为有序序列,然后把后面的数视为要依次插入的序列。

2、图示

 

 

 

 

3、代码实战

①、代码

 

复制代码
 1 #include<stdio.h>
 2 #include<stdlib.h>
 3 #include<time.h>
 4 //顺序表数据结构定义
 5 typedef struct{
 6     int *elem;//存储元素的起始地址
 7     int len;//元素个数
 8 }SSTable;
 9 
10 //初始化顺序表
11 void Init_ST(SSTable &ST,int len)
12 {
13     ST.elem=(int*)malloc(sizeof(int)*len);
14     ST.len=len;
15     //随机数生成
16     srand(time(NULL));
17     int i;
18     for(i=0;i<ST.len;i++)
19     {
20         ST.elem[i]=rand()%100;//生成的随机数是0-99之间
21     }
22 }
23 
24 //打印数组中的元素
25 void Print_ST(SSTable ST)
26 {
27     int i=0;
28     for(i=0;i<ST.len;i++)
29     {
30         printf("%3d",ST.elem[i]);
31     }
32     printf("\n");
33 }
34 
35 //插入排序
36 void Insert_Sort(int *A,int len)
37 {
38     int i,j,insertVal;
39     for(i=1;i<len;i++)//控制要插入的数
40     {
41         insertVal=A[i];//先保存要插入的值
42         //内存控制比较j,要大于等于0,同时A[j]大于insertVal时,arr[j]位置元素往后覆盖
43         for(j=i-1;j>=0&&A[j]>insertVal;j--)
44         {
45             A[j+1]=A[j];
46         }
47         A[j+1]=insertVal;
48     }
49 }
50 
51 
52 //主函数
53 int main()
54 {
55     SSTable ST;
56     Init_ST(ST,10);
57     Print_ST(ST);//打印排序前的顺序表
58     Insert_Sort(ST.elem,10);
59     printf("插入排序后的结果是:\n");
60     Print_ST(ST);
61     return 0;
62 }
复制代码

 

②、结果

 

4、复杂度分析

随着有序序列的不断增加,插入排序比较次数也会增加,插入排序的执行次数也是从1增加到N-1,总运行次数N(N-1)/2,时间复杂度依旧是O(n2)。因为没有使用额外的空间(额外空间必须与输入元素N有关),所以空间复杂度是O(1)。如果数组本身有序,那么就是最好的时间复杂度O(n)。

六、选择排序

选择排序分类

①、简单选择排序

②、堆排序

七、简单选择排序

1、原理:假设排序表L[1,……,n],第i趟排序即从L[i…,n]中选择关键字最小的元素与L(i)交换,每一趟排序可以确定一个元素的最终位置,这样经过n-1趟排序就可以使得整个排序表有序。

2、图示

 

3、代码实战

①、代码

 

复制代码
 1 #include<stdio.h>
 2 #include<stdlib.h>
 3 #include<time.h>
 4 //顺序表数据结构定义
 5 typedef struct{
 6     int *elem;//存储元素的起始地址
 7     int len;//元素个数
 8 }SSTable;
 9 
10 //初始化顺序表
11 void Init_ST(SSTable &ST,int len)
12 {
13     ST.elem=(int*)malloc(sizeof(int)*len);
14     ST.len=len;
15     //随机数生成
16     srand(time(NULL));
17     int i;
18     for(i=0;i<ST.len;i++)
19     {
20         ST.elem[i]=rand()%100;//生成的随机数是0-99之间
21     }
22 }
23 
24 //打印数组中的元素
25 void Print_ST(SSTable ST)
26 {
27     int i=0;
28     for(i=0;i<ST.len;i++)
29     {
30         printf("%3d",ST.elem[i]);
31     }
32     printf("\n");
33 }
34 
35 //交换两个元素
36 void swap(int &a,int &b)
37 {
38     int tmp=a;
39     a=b;
40     b=tmp;
41 }
42 
43 //选择排序
44 void Selete_Sort(int *A,int len)
45 {
46     int i,j,min;//min记录最小元素的下标
47     for(i=0;i<len-1;i++)//最多可以为8
48     {
49         min=i;//认为第i个元素最小
50         for(j=i+1;j<len;j++)//最多可以为9
51         {
52             if(A[j]<A[min])
53             {
54                 min=j;
55             }
56         }
57         //遍历完毕找到最小值的位置后,与A[i]交换,这样最小值就到了最前面
58         if(i!=min)
59         {
60             swap(A[i],A[min]);
61         }
62     }    
63 }
64 
65 
66 //主函数
67 int main()
68 {
69     SSTable ST;
70     Init_ST(ST,10);
71     Print_ST(ST);//打印排序前的顺序表
72     Selete_Sort(ST.elem,10);
73     printf("选择排序后的结果是:\n");
74     Print_ST(ST);
75     return 0;
76 }
复制代码

 

②、结果

 

4、复杂度分析

①、分析:选择排序虽然减少了交换次数,但是循环比较次数依旧和冒泡排序的数量是一样的,都是从1增加到N-1,总运行次数为N(N-1)/2。时间复杂度仍旧是O(n2),因为未使用额外的空间(额外空间必须与输入元的个数N有关),所以空间复杂度是O(1)。

②、总结

时间复杂度:O(n2)
空间复杂度:O(1)

八、堆排序

1、原理

①、基本概念:堆(Heap)是计算机科学中一种特殊的树状数据结构。若满足以下特性,则可以被称作堆

a.给定堆中任意结点P和C,若P是C的父节点,则P的值小于等于(或者大于等于)C的值。

b.若父节点的值恒小于等于子节点的值,则称该堆为最小堆;反之,若父节点的值恒大于等于子节点的值,则该堆称为最大堆。

c.堆中最顶端的那个结点称为根结点。在工作中,一般把最小堆称作小根堆或者小顶堆,把最大堆称为大根堆或者大顶堆。

2、图示

 

 

 

 

 

 

3、代码实战

①、代码

 

复制代码
 1 #include<stdio.h>
 2 #include<stdlib.h>
 3 #include<time.h>
 4 //顺序表数据结构定义
 5 typedef struct{
 6     int *elem;//存储元素的起始地址
 7     int len;//元素个数
 8 }SSTable;
 9 
10 //初始化顺序表
11 void Init_ST(SSTable &ST,int len)
12 {
13     ST.elem=(int*)malloc(sizeof(int)*len);
14     ST.len=len;
15     //随机数生成
16     srand(time(NULL));
17     int i;
18     for(i=0;i<ST.len;i++)
19     {
20         ST.elem[i]=rand()%100;//生成的随机数是0-99之间
21     }
22 }
23 
24 //打印数组中的元素
25 void Print_ST(SSTable ST)
26 {
27     int i=0;
28     for(i=0;i<ST.len;i++)
29     {
30         printf("%3d",ST.elem[i]);
31     }
32     printf("\n");
33 }
34 
35 //交换两个元素
36 void swap(int &a,int &b)
37 {
38     int tmp=a;
39     a=b;
40     b=tmp;
41 }
42 
43 //调整子树
44 void AdjustDown(int *A,int k,int len)
45 {
46     int dad=k;
47     int son=2*dad+1;//左孩子下标
48     while(son<=len)
49     {
50         if(son+1<=len&&A[son]<A[son+1])//判断有没有右孩子,比较左右孩子选大的
51         {
52             son++;
53         }
54         if(A[son]>A[dad])//比较孩子和父亲,如果孩子大于父亲,那么进行交换
55         {
56             swap(A[son],A[dad]);
57             dad=son;//孩子重新作为父亲,判断下一棵子树
58             son=dad*2+1;
59         }
60         else
61         {
62             break;
63         }
64     }
65 }
66 
67 
68 
69 
70 //用数组取表示树,类似于层次建树
71 void Heap_Sort(int *A,int len)
72 {
73     int i;
74     //建立大根堆
75     for(i=len/2;i>=0;i--)
76     {
77         AdjustDown(A,i,len);
78     }
79     swap(A[0],A[len]);//交换顶部和数组最后一个元素
80     //方法是:不断调整剩余元素为大根堆,因为根部最大,所以再次和A[i]交换(相当于放到数组后面),循环往复
81     for(i=len-1;i>0;i--)
82     {
83         AdjustDown(A,0,i);//剩余元素调整为大根堆
84         swap(A[0],A[i]);
85     }
86 }
87 
88 
89 //主函数
90 int main()
91 {
92     SSTable ST;
93     Init_ST(ST,10);
94     Print_ST(ST);//打印排序前的顺序表
95     Heap_Sort(ST.elem,10);//所有元素参与排序
96     printf("堆排序后的结果是:\n");
97     Print_ST(ST);
98     return 0;
99 }
复制代码

 

②、结果

 

4、复杂度分析

①、AdjustDown函数循环的次数是log2n,Heap_Sort函数的第一个for循环了n/2次,第二个for循环循环了n次,总计是3/2nlog2n次,因此时间复杂度是O(nlog2n)。

②、堆排序的最好、最坏、平均时间复杂度都是O(nlog2n)。

③、堆排的空间复杂度是O(1),因为没有使用与n相关的额外空间。

九、归并排序

1、原理解析

 

如上图所示,把每两个元素归为一组,进行小组内排序,然后再次把两个有序小组合并为一个有序小组,不断进行,最终合并成一个有序数组。

 

2、代码实战

①、代码

 

复制代码
 1 #include<stdio.h>
 2 #include<stdlib.h>
 3 #define N 7
 4 //合并操作
 5 void Merge(int *A,int low,int mid,int high)
 6 {
 7     static int B[N];//加static的目的是无论递归调用多少次,都只有一个B[N]
 8     int i,j,k;
 9     for(k=low;k<=high;k++)//复制元素到B中
10     {
11         B[k]=A[k];
12     }
13     for(i=low,j=mid+1,k=i;i<=mid&&j<=high;k++)//合并两个有序数组
14     {
15         if(B[i]<B[j])
16         {
17             A[k]=B[i++];
18         }
19         else
20         {
21             A[k]=B[j++];
22         }
23     }
24     while(i<=mid)//如果有剩余元素,接着放入即可
25     {
26         A[k++]=B[i++];
27     }
28     while(j<=high)
29     {
30         A[k++]=B[j++];//后一半有剩余元素放入
31     }
32 }
33 
34 //归并排序不限制是两两归并,此处使用两两归并
35 void MergeSort(int *A,int low,int high)
36 {
37     if(low<high)
38     {
39         int mid=(low+high)/2;
40         MergeSort(A,low,mid);//排序好前一半
41         MergeSort(A,mid+1,high);//排序好后一半
42         Merge(A,low,mid,high);//将两个有序数组合并
43     }
44 }
45 
46 //打印数组
47 void print(int *a)
48 {
49     int i;
50     for(i=0;i<7;i++)
51     {
52         printf("%3d",a[i]);
53     }
54     printf("\n");
55 }
56 
57 
58 //主函数
59 int main()
60 {
61     int A[7]={49,38,65,97,76,13,27};//数组,共有7个元素
62     MergeSort(A,0,6);
63     print(A);
64     return 0;
65 }
复制代码

 

②、结果

 

4、复杂度分析

①、分析:MergeSort函数的递归次数是log2n,Merge函数循环了n次,因此时间复杂度是O(nlog2n)。

②、总结:

归并排序的最好、最坏、平均时间复杂度都是O(nlog2n)。

归并排序的空间复杂度是O(n),因为使用了数组B,它的大小和A一样,占用了n个元素空间。

十、所有排序算法时间与空间复杂度汇总

1、图示

 

2、注意

①、稳定性是指排序前后,相等元素位置是否会被交换。

②、复杂性是指代码编写的难度。

posted @   Alkaid*  阅读(277)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· DeepSeek在M芯片Mac上本地化部署
点击右上角即可分享
微信分享提示