排序是将一组数据按照特定的顺序(如升序或降序)进行重新排列的过程。
时间复杂度:表示算法运行所需的时间与输入规模之间的关系。
通常使用大 O 记号来表示,例如 O(n)、O(n^2) 等。其中 n 表示输入的规模(例如数组的长度、节点的数量等)。如果一个算法的时间复杂度为 O(n),意味着算法的执行时间与输入规模 n 呈线性关系;如果是 O(n^2),则执行时间与 n 的平方成正比,随着 n 的增大,算法的运行时间增长得更快。
空间复杂度:是衡量算法在运行过程中所需额外存储空间的度量。
它同样使用大 O 记号表示,例如 O(n)、O(1) 等。
如果空间复杂度为 O(n),表示算法需要与输入规模 n 成正比的额外空间来存储数据或进行计算;如果是 O(1),表示算法所需的额外空间是固定的,不随输入规模的变化而变化。
(一)选择排序
思想:在合适的位置选择合适的数。
例:数组a[]:
①当下标为0时,遍历除自己以外的所有在数组中的数,如果有比自己小的数,就交换过来,放在a[0]的空间中,直至比较完所有的数,这样就找到了数组中最小的数,并将它放在了a[0]的位置。
②当下标1时,从自己的后一位数开始遍历,如果有比自己小的数,就交换过来,放在a[1]的空间中,直至比较完所有的数。
以此类推,确定下标为2、3、4、······· len-2位置上的值,排序结束,最后数组中的数为升序排列。
例:
#include <stdio.h> int main(void) { //选择排序 int i, j, temp; int a[] = {3,1,7,2,8,4,5,6}; int len = sizeof(a)/sizeof(a[0]); for(i=0; i<len-1; ++i)//要确定的位置,len-1=7,是下标的最大位置 { for(j=i+1; j<len; ++j)//内层循环给对应位置找合适的数。 { i=0时循环一次,遍历除自己以外的所有数 if(a[j]<a[i]) { temp = a[i]; a[i] = a[j]; a[j] = temp; } } } for(i=0; i<len; ++i) { printf("a[%d] = %d\n",i,a[i]); } return 0; }
(二)冒泡排序
思想:一次冒出一个数,相邻两个数,小的放前,大的放后。
(1)外层循环:
控制排序的轮数,轮数为 (len-1) ,len 为数组长度。
(2)内层循环:
用于每一轮比较和交换相邻的元素。重复地比较要排序的数列的相邻两个数,如果顺序不对则进行交换,并一直重复这样的比较操作,直到没有要交换的数据元素为止。每一轮过后,最大的元素就“浮”到了数组的末尾。经过( len
- 1)
轮排序后,数组就变成了有序的。冒泡排序和选择排序的平均时间复杂度和最坏时间复杂度均为 O(n²),其中 n 是待排序数组的长度。
例:
#include <stdio.h> int main(void) { int i,j,temp; int a[] = {3,1,7,2,8,4,5,6}; int len = sizeof(a)/sizeof(a[0]); for(i=len-1; i>0; --i) { for(j=0; j<i; ++j) { if(a[j]>a[j+1]) { temp = a[j]; a[j] = a[j+1]; a[j+1] = temp; } } } for(i=0; i<len; ++i) { printf("%d ",a[i]); } putchar('\n'); return 0; }
(三)插入排序
思想:在有序序列中,找到合适的位置插入。
(1)非原地插入排序:
每次从未排序的数组 a[] 中选择一个元素,创建一个新的数组 b[] 来放置从没有排序的数组中取出的元素,每次从 a[] 中新取出的元素都要与数组 b[] 相比较,直到未排序部分为空。
(2)原地插入排序:
先把数组 b[] 的数据构建为有序序列,对于未排序数组 a[] ,每次从 a[] 中取出的元素时,都要在数组 b[] 中从后向前扫描,找到相应位置并插入,直到整个数组有序。
原地插入排序比非原地插入排序的空间复杂度低。
非原地插入排序:
#include<stdio.h> int main(void) { int i,j,temp; int a[] = {3,2,5,1,4}; int len = sizeof(a)/sizeof(a[0]); int b[len]; for(i=0; i<len; ++i) { temp = a[i]; j = i; while(j>0 && temp<b[j-1]) { b[j] =b[j-1]; --j; } b[j] = temp; } for(i=0; i<len; ++i) printf("%d ",b[i]); putchar('\n'); return 0; }
原地插入排序:
#include<stdio.h> int main(void) { int i,j,temp; int a[] = {3,2,5,1,4}; int len = sizeof(a)/sizeof(a[0]); for(i=1; i<len; ++i) { temp = a[i]; j = i; while(j>0 && temp<a[j-1]) { a[j] = a[j-1]; --j; } a[j] = temp; } for(i=0; i<len; ++i) printf("%d ",a[i]); putchar('\n'); return 0; }
(四)二分查找(折半查找)
思想:将待查找的区间不断地对半分割,每次比较中间元素与目标值的大小,然后根据比较结果缩小查找区间,直到找到目标值或者确定目标值不存在。
二分查找的前提是数组必须是有序的。
具体的查找思路:
(1)首先,定义查找区间的左右边界 left 和 right,初始时 left = 0,right = len- 1,其中 len 是数组的长度。
(2)计算中间位置 mid = (left + right) / 2 。
(3)将中间元素 arr[mid] 与目标值 target 进行比较:①如果 arr[mid] == target,则查找成功,返回中间位置 mid 。
② 如果 arr[mid] > target,则说明目标值在左半区间,将右边界更新为 right = mid - 1 ,然后 继续在左半区间查找。
③如果 arr[mid] < target,则说明目标值在右半区间,将左边界更新为 left = mid + 1 ,然后 继续在右半区间查找。
(4)重复步骤 2 和 3,直到找到目标值或者 left > right,表示目标值不存在,查找结束。
二分查找的效率较高。
例:
#include<stdio.h> int mian(void) { int begin, mid, end,m; scanf("%d",&m); int a[] = {1,2,3,4,5,6,7,8}; int len = sizeof(a)/sizeof(a[0]); begin = 0; end = len -1; while(begin <= end) { mid = (begin+end)/2; if (a[mid] > m) { end = mid - 1; } else if (a[mid] < m) { begin = mid + 1; } else { break; } } if(begin <= end) printf("yes\n"); else printf("no\n"); return 0; }