数据结构与算法--排序算法
冒泡排序
数据左右比较,把较大的数据交换到右边,往后重复以上操作,直到把最大的数据交换到最后,特点是该算法对数据的有序性敏感,如果在一次的排序过程中没有发生一次交换,那么就意味着数据已经有序,可以立即停止排序
适合待排序的数据基本有序时,则冒泡的效率非常高
时间复杂度:平均:O(N^2) 最优O:(N) 稳定的
void bubble_sort(int* arr,size_t len)
{
bool flag = true;
for(int i = len-1;i > 0 && flag;i--)
{
flag = false;
for(int j = 0;j < i;j++)
if(arr[j] > arr[j+1])
{
SWAP(arr[j],arr[j+1]);
flag = true;
}
}
}
选择排序
假定最开始的位置是最小值并记录下标min,然后与后面所有的数据比较,如果有比min位置的值更小的数,那么更新min,结束后min的下标与开始时发生过改变,才进行交换到开始位置
虽然时间复杂度较高,但是数据交换次数比较少,因此实际的运行速度并不慢(数据交换比数据比较耗时)
时间复杂度:O(N^2) 不稳定的
void selection_sort(int* arr,size_t len)
{
for(int i = 0;i < len;i++)
{
int min = i;
for(int j = i+1;j < len;j++)
{
if(arr[j] < arr[min])min = j;
}
if(min != i)SWAP(arr[i],arr[min]);
}
}
插入排序
把数据看成两个部分,前部分是有序的,把剩余部分的数据逐个往前比较,如果比前面的数小,前面的数往后移动一位,继续往前比较,直到遇到更小的数,那么该数据的后一个位置即为可以插入的位置
适合对已经排序后的数据,新增数据后再排序
时间复杂度:O(N^2)
void insertion_sort(int* arr,int len)
{
for(int i = 1,j;i < len;i++)
{
int val = arr[i];
for(j=i;j > 0 && arr[j-1] > val;j--)
{
arr[j] = arr[j-1];
}
if(i != j)arr[j] = val;
}
}
希尔排序
是插入排序的增强版,由于插入排序时数据移动的步长较短,都是1,所以增加了增量的概念,通过不停地缩减增量,最终步长还是变回1,可以提高排序效率
当待排序数据远离最终位置时,希尔排序的效率高于插入排序
时间复杂度:O(NlogN)~O(N^2)
void shell_sort(int* arr,size_t len)
{
for(int k = len/2;k > 0;k /= 2)
{
for(int i = k,j;i < len;i++)
{
int val = arr[i];
for(j = i;j-k >= 0 && arr[j-k] > val;j-=k)
{
arr[j] = arr[j-k];
}
if(i != j)arr[j] = val;
}
}
}
快速排序
在待排序数据中先找一个标杆位置p,备份p位置的值val,记录左标杆l、右标杆r,l从p的左边找比val大的数,找到后赋值给p位置,更新p到l,然后r从p的右边找比val小的数,找到后赋值给p位置,更新p到r
循环往复,直到l和r重合,把val还原回p位置完成一次快排,然后用同样的方式对p左右两边的数据进行快排
它的综合性能最优,因此得名快速排序
时间复杂度:O(NlogN)
void _quick(int* arr,int left,int right)
{
if(left >= right)return;
int l = left,r = right,p = left;
int val = arr[p];
while(l > r)
{
while(p < r && val < arr[r])r--;
if(p < r)
{
arr[p] = arr[r];
p = r;
}
while(l < p && arr[l] < val)l++;
if(l < p)
{
arr[p] = arr[l];
p = l;
}
}
arr[p] = val;
_quick(arr,left,p-1);
_quick(arr,p+1,right);
}
void quick_sort(int* arr,size_t len)
{
_quick(arr,0,len-1);
}
归并排序
首先需要把数据拆分成单独的个体,然后按照从小到大的顺序比较后排序到一段临时空间中,把排序后的临时空间中的数据拷贝回原内存,然后依次把有序的个体继续以上操作合并成更大的有序部分
归并排序不需要交换数据,减少了交换的耗时,但是需要额外的存储空间,是一种典型的以空间换时间的算法
时间复杂度:O(N*logN)
void __merge(int* arr,int* temp,int l,int p,int r)
{
if(arr[p] < arr[p+1])return;
int i = l,j = p+1;
int s = l;
while(i <= p && j <= r)
{
if(arr[i] <= arr[j])
temp[s++] = arr[i++];
else
temp[s++] = arr[j++];
}
while(i <= p)temp[s++] = arr[i++];
while(j <= r)temp[s++] = arr[j++];
memcpy(arr+l,temp+l,(r-l+1)*sizeof(int));
}
void _merge(int* arr,int* temp,int l,int r)
{
if(l >= r)return;
int p = (l + r)/2;
_merge(arr,temp,l,p);
_merge(arr,temp,p+1,r);
__merge(arr,temp,l,p,r);
}
void merge_sort(int* arr,size_t len)
{
int* temp = malloc(sizeof(int)*len);
_merge_sort(arr,temp,0,len-1);
free(temp);
}
堆排序
把待排序数据当做完全二叉树看待,先把二叉树调整成大顶堆结构,把根节点的值与末尾节点的值交换,然后数量范围-1,重新从上到下调整回大顶堆,继续以上操作,直到数量为1结束,此时全部数据就从小到大排序了
既可以顺序实现,也可以递归实现
时间复杂度:O(N*logN)
void heap_sort(int* arr,int len)
{
if(arr == NULL)return;
// 把数组调整成堆结构
for(int i = 1;i < len;i++)
{
int number = i+1;
while(number/2 >= 1)
{
if(arr[number-1] > arr[number/2-1])
{
SWAP(arr[number-1],arr[number/2-1]);
number /= 2;
continue;
}
break;
}
}
// 堆顶交换到末尾 从堆顶到末尾-1 调整回堆 直到完成
int cnt = len;
for(int i = 0;i < len-1;i++)
{
SWAP(arr[0],arr[len-i-1]);
cnt--;
int n = 1;
while(n-1 < cnt)
{
// 有左右子树
if(n*2 < cnt)
{
// 左比右大 且比根大
if(arr[n*2-1] >= arr[n*2] && arr[n*2-1] > arr[n-1])
{
SWAP(arr[n-1],arr[n*2-1]);
n *= 2;
}
else if(arr[n*2] > arr[n*2-1] && arr[n*2] > arr[n-1])
{
SWAP(arr[n-1],arr[n*2]);
n = n*2+1;
}
else break;
}
else if(n*2 - 1 < cnt)
{
if(arr[n*2-1] > arr[n-1])
{
SWAP(arr[n-1],arr[n*2-1]);
n = n*2;
}
else
break;
}
else
break;
}
}
}
// 对从top下标到end下标进行调整成堆
void _heap_sort(int* arr,int top,int end)
{
if(top >= end)return;
int max = top+1; // 根左右中的最大值编号
int l = max * 2; // 左子树编号
int r = max * 2 + 1;// 右子树编号
if(l - 1 <= end && arr[l-1] > arr[max-1])
max = l;
if(r - 1 <= end && arr[r-1] > arr[max-1])
max = r;
if(max != top + 1)
{
// 最大值不是根
SWAP(arr[top],arr[max-1]);
_heap_sort(arr,max-1,end);
}
}
// 递归实现堆排序
void heap_sort_recursion(int* arr,int len)
{
for(int i = 1;i < len;i++)
{
int number = i+1;
while(number/2 >= 1)
{
if(arr[number-1] > arr[number/2-1])
{
SWAP(arr[number-1],arr[number/2-1]);
number /= 2;
continue;
}
break;
}
}
for(int i = len-1;i > 0;i--)
{
SWAP(arr[0],arr[i]);
_heap_sort(arr,0,i-1);
}
}
计数排序
1.找出待排序数据中的最大、最小值,创建长度为 最大值-最小值+1 的哈希表
2.根据哈希函数 数据-最小值 当做哈希表的下标并对所有数据做标记
3.从头遍历哈希表,当某个位置的值大于0,把该位置的下标+最小值的到结果依次放回原数据内存中
是一种典型的以空间换时间的算法 理论上该算法的速度是非常快的,它不是基于比较的排序算法,在一定范围内的整数排序中快于任意一种基于比较的排序算法,但是有很大的局限性,只适合整型数据排序,数据的差值不宜太大,否则会非常浪费内存 反之数据差值不大、重复数比较多的情况下性价比很高
时间复杂度:Ο(n+k)(其中k是整数的范围) 稳定的
void count_sort(TYPE* arr,size_t len)
{
TYPE max = arr[0],min = arr[0];
for(int i=1; i<len; i++)
{
if(arr[i] > max) max = arr[i];
if(arr[i] < min) min = arr[i];
}
TYPE* temp = calloc(sizeof(TYPE),max-min+1);
for(int i=0; i<len; i++)
{
temp[arr[i]-min]++;
}
for(int i=0,j=0; i<max-min+1; i++)
{
while(temp[i]--) arr[j++] = i+min;
}
free(temp);
}
桶排序
一般是把数据根据数值分到不同的"桶",通过不同的、合适的其他排序算法对"桶"中的数据进行排序,然后再把各个"桶"的数据依次拷贝回原内存中
桶排序是一种降低排序规模的排序思想,也是以空间换时间的算法
缺点:如何分桶、桶如何定义,都需要针对具体待排序的数据进行分析后才能确定
桶排序的稳定性取决于桶内排序使用的算法 有些资料上认为桶排序可以做到稳定,所以认为桶排序稳定
void _bucket(TYPE* arr,size_t len,int cnt,TYPE range)
{
// 申请桶内存
// bucket_s指向桶的开头位置
// bucket_e指向桶的末尾 接下去要入桶的位置
TYPE* bucket_s[cnt],*bucket_e[cnt];
for(int i=0; i<cnt; i++)
{
// 数据有可能全在一个桶内
bucket_s[i] = malloc(sizeof(TYPE)*len);
bucket_e[i] = bucket_s[i];
}
// 把数据按照对应的范围放入对应的桶中
for(int i=0; i<len; i++)
{
for(int j=0; j<cnt; j++)
{
if(j*range <= arr[i] && arr[i] < (j+1)*range)
{
*(bucket_e[j]) = arr[i];
bucket_e[j] += 1;
break;
}
}
}
// 通过其他排序对各个桶中的数据排序
for(int i=0; i<cnt; i++)
{
// 计算桶中元素的数量
int size = bucket_e[i] - bucket_s[i];
// 其他排序
if(1 < size)
bubble_sort(bucket_s[i],size);
// 按照先后顺序 放入原内存中
memcpy(arr,bucket_s[i],size*sizeof(TYPE));
arr += size;
free(bucket_s[i]);
}
}
// 桶排序
void bucket_sort(TYPE* arr,size_t len)
{
_bucket(arr,len,4,25);
}
基数排序
原理是将整数按位数切割成不同的数字,然后按每个位数分别比较。基数排序的方式可以采用LSD(Least significant digital)或MSD(Most significant digital),LSD的排序方式由键值的最右边开始,而MSD则相反,由键值的最左边开始。
- MSD:先从高位开始进行排序,在每个关键字上,可采用计数排序
- LSD:先从低位开始进行排序,在每个关键字上,可采用桶排序
① 将所有待比较数值(正整数)统一为同样的数位长度,数位较短的数前面补零。
② 从最低位开始,依次进行一次排序。
③ 这样从最低位排序一直到最高位排序完成以后, 数列就变成一个有序序列。
void radix_sort(TYPE* arr,size_t len)
{
ListQueue* queue[10] = {};
for(int i=0; i<10; i++)
{
queue[i] = create_list_queue();
}
// 循环次数由最大值的位数决定
TYPE max = arr[0];
for(int i=1; i<len; i++)
{
if(arr[i] > max) max = arr[i];
}
// i是1表示处理个位,2表示处理十位...
for(int i=1,k=1; max/k>0; k*=10,i++)
{
int mod = pow(10,i);
int div = mod/10;
for(int j=0; j<len; j++)
{
// 获取arr[j] 某位的数
int index = arr[j]%mod/div;
push_list_queue(queue[index],arr[j]);
}
// 对所有队列依次出队回原内存中
for(int j=0,i=0; j<10; j++)
while(!empty_list_queue(queue[j]))
{
arr[i++] = head_list_queue(queue[j]);
pop_list_queue(queue[j]);
}
}
for(int i=0; i<10; i++)
{
destroy_list_queue(queue[i]);
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具