链接:https://blog.youkuaiyun.com/xu__jun/article/details/78331111
1、选择排序-简单选择排序(Selection-sort)
选择排序(Selection-sort)是一种简单直观的排序算法。它的工作原理:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。
时间复杂度:O(n^2) 空间复杂度:O(1)
实现:
void selectSort(int arr[],int n) { int i,j,minValue,tmp; //一趟确定一个有序元素,最后一次不要确定,所以只需n-1次 for(i=0;i<n-1;i++) { minValue=i; // 确定无序区最小元素下标 for(j=i+1;j<n;j++) { if(arr[minValue]>arr[j]) { minValue=j; } } //如果无序区第一个元素最小,则没有必要交换 if(minValue!=i) { tmp=arr[i]; arr[i]=arr[minValue]; arr[minValue]=tmp; } } } //打印函数 printArray(int arr[],int n) { int i; for(i=0;i<n;i++) { printf("%d\t",arr[i]); } printf("\n"); } int main() { int arr[10]={28,9,7,6,5,1,3,11,45,66}; printArray(arr,10); selectSort(arr,10); printArray(arr,10); return 0; }
如上所示,简单的选择排序复杂度固定为O(n^2),每次内循环找出没有排序数列中的最小值,然后跟当前数据进行交换。由于选择排序通过直接查找最值得方法进行排序,循环次数几乎是固定的(n次)
优化:(链接:https://blog.youkuaiyun.com/hansionz/article/details/80862590)
另外一种优化方法是每次循环同时找出最大值和最小值可以使循环减少为(n/2次)
优化实现:
void selectSort(int arr[],int n) { int left=0; int right=n-1; while(left<right) { int max=left; //记录无序区最大元素下标 int min=left; //记录无序区最小元素下标 int j=0; for(j=left+1;j<=right;j++) { //找出最小元素的下标 if(arr[j]<arr[min]) { min=j; } //找出最大元素的下标 if(arr[j]>arr[max]) { max=j; } } //最小值若是第一个则没有必要交换 if(min!=left) { int tmp=arr[left]; arr[left]=arr[min]; arr[min]=tmp; } //若最大元素的下标是left,前面已经和最小元素交换过了,此时最大元素的下标应该是min if(max==left) { max=min; } //最大值如果是最后一个则没必要交换 if(max!=right) { int tmp=arr[right]; arr[right]=arr[max]; arr[max]=tmp; } left++; right--; } } //打印函数 printArray(int arr[],int n) { int i; for(i=0;i<n;i++) { printf("%d\t",arr[i]); } printf("\n"); } int main() { int arr[10]={10,9,8,7,6,5,4,3,2,1}; printArray(arr,10); selectSort(arr,10); printArray(arr,10); return 0; }
2、冒泡排序(Bubble Sort)
冒泡排序是一种简单的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。
时间复杂度:O(n^2) 空间复杂度:O(1)
代码实现:
bubbleSort(int arr[],int n) { int i,j,tmp; for(i=0;i<n-1;i++) //最后一趟不用比较,故n-1次 { for(j=1;j<n;j++) { if(arr[j]<arr[j-1]) { tmp=arr[j]; arr[j]=arr[j-1]; arr[j-1]=tmp; } } } } //打印函数 printArray(int arr[],int n) { int i; for(i=0;i<n;i++) { printf("%d\t",arr[i]); } printf("\n"); } int main() { int arr[10]={10,9,8,7,6,5,4,3,2,1}; printArray(arr,10); bubbleSort(arr,10); printArray(arr,10); return 0; }
优化:
(1)、对于外层循环,如当前序列已经有序,即不再进行交换,应该不再进行接下来的循环直接跳出
(2)、对于内层循环后面最大值1已经有序的情况应该不再进行循环
代码实现:
bubbleSort(int arr[],int n) { int i,nflag,tmp; do { nflag=0; for(i=0;i<n-1;i++) { if(arr[i]>arr[i+1]) { tmp=arr[i+1]; arr[i+1]=arr[i]; arr[i]=tmp; nflag=i+1; } } n=nflag; }while(nflag); //当nflag为0时,说明本次循环没有发生交换,序列已经有序不用再循环,如果nflag>0则记录了随后依次发生交换的位置,还位置以后的序列都是有序的,循环不再往后进行 } //打印函数 printArray(int arr[],int n) { int i; for(i=0;i<n;i++) { printf("%d\t",arr[i]); } printf("\n"); } int main() { int arr[10]={10,9,8,7,6,5,4,3,2,1}; printArray(arr,10); bubbleSort(arr,10); printArray(arr,10); return 0; }
3、插入排序-简单插入排序
插入排序是将一个记录插入到已经有序的序列中,得到一个新的元素加一的有序序列,实现上即将第一个元素看作一个有序的序列,从第二个元素开始逐个插入得到一个完整的有序序列,插入过程:
时间复杂度O(n^2) 空间复杂度O(1)
int InsertSort(int arr[],int n) { int i,j,tmp=0; for(i=1;i<n;i++) { for(j=i;j>0;j--) { if(arr[j]<arr[j-1]) { tmp=arr[j]; arr[j]=arr[j-1]; arr[j-1]=tmp; } else { break; } } } } int PrintArr(int arr[],int n) { int i; for(i=0;i<n;i++) { printf("%d\t",arr[i]); } printf("\n"); } int main() { int arr[10]={2,5,6,4,3,7,9,8,1,0}; PrintArr(arr,10); InsertSort(arr,10); PrintArr(arr,10); return 0; }
优化:
,前面提到选择排序不管什么情况下都是固定为O(n2)的算法,插入算法虽然也是O(n2)的算法,不过可以看出,在已经有序的情况下,插入可以直接跳出循环,在极端情况下(完全有序)插入排序可以是O(n)的算法。不过在实际完全乱序的测试用例中,与本文中的选择排序相比,相同序列的情况下发现插入排序运行的时间比选择排序长,这是因为选择排序每次外循环只与选择的最值进行交换,而插入排序则需要不停与相邻元素交换知道合适的位置,交换的三次赋值操作同样影响运行时间,因此下面对这一点进行优化:
int InsertSort(int arr[],int n) { int i,j,tmp,elem; for(i=1;i<n;i++) { elem=arr[i]; for(j=i;j>0;j--) { if(elem<arr[j-1]) { arr[j]=arr[j-1]; } else { break; } } arr[j]=elem; } } int PrintArr(int arr[],int n) { int i; for(i=0;i<n;i++) { printf("%d\t",arr[i]); } printf("\n"); } int main() { int arr[10]={2,5,6,4,3,7,9,8,1,0}; PrintArr(arr,10); InsertSort(arr,10); PrintArr(arr,10); return 0; }