数据结构-排序算法
一、插入排序(从小到大为例)
算法思想:(直接)插入排序在我看来就是一小段一小段的从前到后分段的排序。从第二个元素开始,设置下标为i,将每轮循环的第i个元素当作Key与其前面的元素做对比。找到下标j对应的arr[j]的值比Key小而arr[j+1]的值比Key大的位置,将其插入,然后继续下一次的循环直到结束。
时间复杂度:O(n²)
#inclue studio
//首先传入一个整型数组arr与排序的数组中元素个数长度len
void InsertSort(int arr[],int len){
int key,i,j;
for(i=1;i<len;i++){
key=arr[i];//为Key赋值
//arr[j]>key指满足第i个元素前面的数组值比Key大
for(j=i-1;j>=0&&arr[j]>key;j--){
arr[j+1]=arr[j];//将满足条件的数组值后移
}
arr[j+1]=key;//最后将key的值插入j+1的位置
}
}
/**
*例如
下标 0 1 2 3 4 5 6 7
①数组值 15 23 7 86 59 4 13 21
↑ (此处指向的为第一轮的i)
此时key为arr[1]=23,前面只有15这一个元素,15<23 无需操作
②数组值 15 23 7 86 59 4 13 21
↑ (此处指向的为第二轮的i)
此时key为arr[2]=7,与j<i-1(j从1开始),即i之前的元素对比,
23>7(后移),15>7(后移),经过两轮对比后,j的值变为-1,跳
出循环,最后执行arr[j+1]=key,将7变为数组第一个元素。
③数组值 7 15 23 86 59 4 13 21
↑ (此处指向的为第三轮的i)
此时key为arr[3]=86,与j<i-1(j从2开始),即i之前的元素对比,
86>23,不满足条件,不进入循环。
④数组值 7 15 23 86 59 4 13 21
↑ (此处指向的为第四轮的i)
此时key为arr[4]=59,与j<i-1(j从3开始),即i之前的元素对比,
86>59(后移),23<59不进入循环。
下面过程与上述以此类推......
**/
二、折半插入排序(从小到大为例)
算法思想:折半插入排序是插入排序基于二分法的优化。直接插入排序在每次与第i个元素前面的元素做对比的时候是顺序对比的,而在i前面的元素是排序完成后的,故只需通过二分法找到第i个元素值应该插入的位置即可。
时间复杂度:O(n²)
#include studio
void midInsertSort(int arr[],int len){
int key,high,low,mid;
for(int i=1;i<len;i++){
key=arr[i];//将第i个元素的值赋值给key,方便后续对比
low=0;high=i-1;//为二分法的高位低位赋下标
while(high>=low){
mid=(low+high)/2;
if(key>arr[mid]) //若key的值大于中间值,则在右侧
low=mid+1;
else //否则在左侧寻找
high=mid-1;
}
//跳出循环后,此时high的位置即为插入位置,
//且low在high右侧,low及其之后的元素需要后移
for(int j=i-1;j>=low;j--){
arr[j+1]=arr[j];
}
//最后将key的值插入到high后方
arr[high+1]=key;
}
}
三、希尔排序(从小到大)
算法思想:希尔排序在我看来是采用类似极限思想的对插入排序的优化。此算法设置一个间隔,间隔一般取数组长度的1/2向下取整。根据此间隔将本来的数组分组,然后在组内使用插入排序,同时减半间隔大小直到间隔为1,达到基本有序后最后一次插入排序,完成排序。
#include stdio
void ShellSort(int arr[],int len){
int key,gap,j;
//设置间隔gap的初值为数组长度的一半(向下取整)
//每次循环后减半间隔gap的值
for(gap=len/2;gap>=1;gap/=2){
//在组内使用插入排序,将gap当为一般插入排序的1即可
for(int i=gap;i<len;i++){
key=arr[i];
for(j=i-gap;j>=0&&arr[j]>key;j-=gap){
arr[j+gap]=arr[j];
}
arr[j+gap]=key;
}
}
}
四、冒泡排序(从小到大)
算法思想:若传入的数组元素个数为n,则默认需要进行n-1趟排序。每趟排序过程中对元素两两进行比对,符合条件进行交换,将最大或最小的值向冒泡一样排序到数组一端。可设置一个bool
类型的变量flag来判断数组是否进行了交换,若无发生交换则代表有序。
时间复杂度:O(n²)
#include stdio
void BubbleSort(int arr[],int len){
for(int i=0;i<len-1;i++){//一共最多len-1次循环
bool flag=false;//设置交换标值的默认值为false
for(int j=0;j<len-1-i;j++){
if(arr[j]>arr[j+1]){
int temp=arr[j+1];
arr[j+1]=arr[j];
arr[j]=temp;
flag=true;
}
}
/**
*内部循环还可以写为以下代码
for(int j=len-1;j>i;j--){
if(arr[j-1]>arr[j]){
int temp=arr[j];
arr[j]=arr[j-1];
arr[j-1]=temp;
flag=true;
}
}
*/
if(flag==false) break;
}
}
五、快速排序(从小到大)
算法思想:快排的思想就是通过设定一个基准值(一般为数组第一个值)来通过low和high两个指针来对比数组中的值确定基准值base的最终排序位置(low为低位,high为高位)。在此期间先向左移动high指针直到找到一个比基准值base小的元素,将此元素放到当前low的位置;随后开始向右移动low指针直到找到比base大的元素,再将此元素放到当前high的位置。按此规律一直进行下去直到low和high指向同一位置,即为base应该在的最终排序位置。这样就将其划分为了另外两个子表,再递归的将划分出的两个子表快排,直到low>high,即整个序列有序。
时间复杂度:最好情况:O(nlog2n
) 最坏情况:O(n²)
快速排序是所有内部排序算法中平均性能最优的排序算法。
#include stdio
int Partition(int low,int high,int arr[]){
int base=arr[low];
while(low<high){//通过low和high来确定base的最终位置
//由于右子表中都是要大于等于base的元素,所以检验high位置的元素与base的大小
//若大于等于base,就让high的值减一,此时high右边的元素皆大于等于base
while(low<high&&arr[high]>=base) high--;
//直到跳出循环,说明找到了arr[high]<base 则将当前值放到low的位置
arr[low]=arr[high];
//由于左子表中都是要小于等于base的元素,所以检验low位置的元素与base的大小
//若小于等于base,就让low的值加一,此时low左边的元素皆小于等于base
while(low<high&&arr[low]<=base) low++;
//直到跳出循环,说明找到了arr[low]>base 则将当前值放到high的位置
arr[high]=arr[low];
}
arr[low]=base;
return low;
}
void QuickSort(int low,int high,int arr[]){
if(low<high){
int basis=Partition(low,high,arr);//划分
QuickSort(low,basis-1,arr);//划分左子表
QuickSort(basis+1,high,arr);//划分右子表
}
}
六、简单选择排序(从小到大)
算法思想:依次从数组元素中找出最小(或最大)的元素放在首位。最后只剩一个元素时不需要再对比排序 。
空间复杂度:O(1) 时间复杂度:O(n²) 稳定性:不稳定
#include studio
void SelectSort(int arr[],int len){
//从第i+1个位置开始确定最小值,直到最后一个元素不需要对比,即进行n-1次处理
for(int i=0;i<n-1;i++){
//第i+1个元素后面其他的元素中与第i+1个元素对比找出最小值
for(int j=i+1;j<n;j++){
if(arr[i]>arr[j]){
int temp=arr[j];
arr[j]=arr[i];
arr[i]=temp;
}
}
}
}
七、堆排序
算法思想:堆排序就是将序列建立成大根堆(或小根堆)的形式,然后将堆顶(也就是数组最大值,建立成大根堆后的数组的第一个元素)与数组的最后一个元素呼互换,以此来将数组最大值放到数组的最后边。然后数组的长度减一,对前面的n-1个元素继续上面的操作,直到数组排序完成。
注:大顶堆即为父节点大于左右孩子的完全二叉树,将大根堆形成的完全二叉树层序遍历即为大根堆的数组。
#include studio
//注:实际运行该函数应该在HeadAdjust下方
void BuildMaxHeap(int arr[],int len){
//取i=len/2是因为其他len/2个元素均为叶结点(终端结点)
//,而大根堆只需要调整非终端结点
for(int i=len/2;i>0;i--){
HeadAdjust(arr,i,len);
}
}
//用以将以i为根的子树处理为大根堆(也就是自底向上的找非单结点的子树,将其处理为大根堆)
void HeadAdjust(int arr[],int i,int len){
arr[0]=arr[i];//将数组的第一个元素用以暂存子树的根结点
//对于完全二叉树层序排列的第i个结点来说,2i为其左孩子结点下标(前提2i<=len)
//2i+1为其右孩子结点(前提2i+1<=len),
for(int j=2*i;j<=len;j*=2){
if(j<len&&arr[j]<arr[j+1])//arr[j]即为左孩子,arr[j+1]为右孩子
j++;//如果右孩子更大则让i指向右孩子(指向谁谁为父结点,找最大是为了满足大根堆)
//此时无论是左子树大还是右子树大,j指向的一定是最大的那一个
if(arr[0]>arr[j]) break;//如果arr[j]<arr[0] 说明子树的根本身就最大
else{//否则说明需要换根,将k的值换掉
arr[i]=arr[j];//将子树最大值换到子树根的位置
//修改i的下标值到j,以便进入下一次循环判断
//以子结点为根的子树是否依旧满足大根堆的要求
i=j;
}
}
//最后子树数组被调整为大根堆后,将被筛选的结点放入最终位置
arr[i]=arr[0];
}
//有了大根堆后,将堆顶和堆底元素互换,然后将剩下的n-1个元素当
//的数组,继续重复大根堆的排序过程
void HeapSort(int arr[],int len){
BuildMaxHeap(arr,len);//先构建大根堆
for(int i=len;i>1;i--){//n-1趟交换和调整大根堆
int temp=arr[i];
arr[i]=arr[1];
arr[1]=temp;
HeadAdjust(arr,1,i-1);
//互换以后第一个元素为根再调整为大根堆
//这里以第一个元素为根是因为第一个元素是与本来的大根堆的根互换而来的
}
}
以上均为个人学习理解``(>人<;)`