目录
[3. 插入排序](#3. 插入排序)
1.冒泡排序
冒泡排序(Bubble Sort)是一种简单的排序算法,它通过重复地遍历待排序的列表,比较相邻的元素并交换它们的位置来实现排序。该算法的名称来源于较小的元素会像"气泡"一样逐渐"浮"到列表的顶端。
时间复杂度:O(n^2)
空间复杂度:O(1)
特点:稳定,效率低
代码实现:
cpp
vector<int> sortArray(vector<int>& nums) {
for(int i=0;i<nums.size();i++){
int flag = false;
for(int j = 1;j<nums.size()-i;j++){
if(nums[j-1]>nums[j]){
flag = true;
swap(nums[j-1],nums[j]);
}
}
if(!flag){
break;
}
}
return nums;
}
2.选择排序
选择排序基本思想是每次从待排序的数据中选择最小(或最大)的元素,放到已排序序列的末尾,直到全部数据排序完成。
时间复杂度:O(n^2)
空间复杂度:O(1)
特点:不稳定,效率低
代码实现
cpp
vector<int> sortArray(vector<int>& nums) {
for(int i=0;i<nums.size();i++){
int index = i ;
for(int j = i+1;j<nums.size();j++){
if(nums[index]>nums[j]){
index = j;
}
}
swap(nums[i],nums[index]);
}
return nums;
}
3. 插入排序
插入排序通过构建有序序列,对于未排序的数据,在已排序序列中从后向前扫描,找到相应位置并插入。插入排序的代码实现虽然没有冒泡排序和选择排序那么简单粗暴,但它的原理应该是最容易理解的了,因为只要打过扑克牌的人都应该能够秒懂。
时间复杂度:O(n^2)
空间复杂度:O(1)
特点:稳定,效率低
代码实现:
cpp
vector<int> sortArray(vector<int>& nums) {
for(int i=1;i<nums.size();i++){
int num = nums[i];
int j = i-1;
while(j>=0&&num<nums[j]){
nums[j+1] = nums[j];
j--;
}
nums[j+1] = num;
}
return nums;
}
4.希尔排序(插入排序的优化)
希尔排序的基本思想是:先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录"基本有序"时,再对全体记录进行依次直接插入排序。
时间复杂度:O(n log n) 到 O(n²)
空间复杂度:O(1)
特点:不稳定,效率相对低。
代码实现
cpp
vector<int> sortArray(vector<int>& nums) {
int n = nums.size();
int gap = n/2;
while(gap>0){
for(int i = gap;i<n;i++){
int temp = nums[i];
int j = i;
while(j>=gap&&nums[j-gap]>temp){
nums[j] = nums[j-gap];
j-=gap;
}
nums[j] = temp;
}
gap/=2;
}
return nums;
}
5.归并排序
归并排序(Merge sort)是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。归并排序的核心思想是将一个大问题分解成若干个小问题,分别解决这些小问题,然后将结果合并起来,最终得到整个问题的解。
时间复杂度:O(n log n)
空间复杂度:O(n)
特点:稳定,效率较高,但需要额外空间。
代码实现
cpp
vector<int> merge_sort(vector<int>& nums){
if(nums.size()<=1){
return nums;
}
int mid = nums.size()/2;
vector<int> left(nums.begin(),nums.begin()+mid);
vector<int> right(nums.begin()+mid,nums.end());
vector<int> merge_left = merge_sort(left);
vector<int> merge_right = merge_sort(right);
return merge(merge_left,merge_right);
}
vector<int> merge( const vector<int>& left,const vector<int>& right){
vector<int> result;
int i=0, j = 0;
while(i<left.size()&&j<right.size()){
if(left[i]<right[j]){
result.push_back(left[i]);
i++;
}
else{
result.push_back(right[j]);
j++;
}
}
while(i<left.size()){
result.push_back(left[i++]);
}
while(j<right.size()){
result.push_back(right[j++]);
}
return result;
}
6.快速排序
快速排序(Quick Sort)是一种高效的排序算法,基于分治法(Divide and Conquer)的思想。它的核心是通过选择一个基准元素(pivot),将列表分为两部分:一部分小于基准元素,另一部分大于基准元素,然后递归地对这两部分进行排序。
时间复杂度:O(n log n)
空间复杂度:O(log n)
特点:不稳定,但效率较高。快速排序的空间复杂度主要来自递归调用栈的深度,而不是额外的数组空间。
代码实现
cpp
void fast_sort(vector<int>& nums,int left,int right){
if(right-left<=1){
return;
}
int index = left+rand()%(right-left);
int value = nums[index];
swap(nums[left],nums[index]);
int i = left+1;
int j = right-1;
while(i<=j){
while(i<=j&&nums[i]<=value) i++;
while(i<=j&&nums[j]>value) j--;
if(i<j){
swap(nums[i],nums[j]);
}
}
swap(nums[left],nums[j]);
fast_sort(nums,left,j);
fast_sort(nums,j+1,right)
}
7.堆排序
堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。堆排序可以说是一种利用堆的概念来排序的选择排序。分为两种方法:
- 大顶堆:每个节点的值都大于或等于其子节点的值,在堆排序算法中用于升序排列;
- 小顶堆:每个节点的值都小于或等于其子节点的值,在堆排序算法中用于降序排列;
时间复杂度:O(n log n)
空间复杂度:O(1)
特点:不稳定,但效率较高。
代码实现
cpp
vector<int> sortArray(vector<int>& nums) {
int n = nums.size();
for(int i = n/2-1;i>=0;i--){
heap_sort(nums,n,i);
}
for(int i = n-1;i>=0;i--){
swap(nums[i],nums[0]);
heap_sort(nums,i,0);
}
return nums;
}
void heap_sort(vector<int>& nums,int n,int i){
int max = i;
int left = 2*i+1;
int right = 2*i+2;
if(left<n&& nums[left]>nums[max]) max = left;
if(right<n&& nums[right]>nums[max]) max = right;
if(max!=i) {
swap(nums[max],nums[i]);
heap_sort(nums,n,max);
}
}
8.计数排序
计数排序(Counting Sort)是一种非比较型的排序算法,适用于对整数或有限范围内的数据进行排序。它的核心思想是通过统计每个元素的出现次数,然后根据统计结果将元素放回正确的位置。计数排序的时间复杂度为 O(n + k),其中 n 是待排序元素的数量,k 是数据的范围大小。
计数排序的核心在于将输入的数据值转化为键存储在额外开辟的数组空间中。作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数。
时间复杂度:O(n + k)
空间复杂度:O(n + k)
特点:当 k 较小时,性能优异,稳定(逆序遍历原数组,正序是不稳定的)。
代码实现
cpp
vector<int> sortArray(vector<int>& nums) {
int max_num = INT_MIN;
for(int num:nums){
max_num = max(max_num,num);
}
vector<int> count(max_num+1);
for(int num:nums){
count[num]++;
}
for(int i=1;i<count.size();i++){
count[i]+=count[i-1];
}
vector<int> result(count[count.size()-1],0);
for(int i=nums.size()-1;i>=0;--i){
result[count[nums[i]]-1] = nums[i];
count[nums[i]]-=1;
}
return result;
}
9.桶排序
桶排序(Bucket Sort)是一种分布式排序算法,它将待排序的元素分配到若干个桶(Bucket)中,然后对每个桶中的元素进行排序,最后将所有桶中的元素按顺序合并。桶排序的核心思想是将数据分到有限数量的桶中,每个桶再分别排序(可以使用其他排序算法或递归地使用桶排序)。
桶排序是计数排序的升级版。它利用了函数的映射关系,高效与否的关键就在于这个映射函数的确定。为了使桶排序更加高效,我们需要做到这两点:
- 在额外空间充足的情况下,尽量增大桶的数量
- 使用的映射函数能够将输入的 N 个数据均匀的分配到 K 个桶中
同时,对于桶中元素的排序,选择何种比较排序算法对于性能的影响至关重要。
时间复杂度:O(n + k * m log m)
空间复杂度:O(n + k)
特点:数据分布均匀时,性能优异。
代码实现
cpp
vector<int> sortArray(vector<int>& nums) {
int n = nums.size();
if(n<=1) return nums;
int max_num = *max_element(nums.begin(),nums.end());
int min_num = *min_element(nums.begin(),nums.end());
int bucketNum = static_cast<float>(max_num-min_num)/n+1;
vector<vector<int>> buckets(bucketNum);
for(int num:nums){
int bucketIndex = (num-min_num)/n;
buckets[bucketIndex].push_back(num);
}
for(auto& bucket: buckets){
sort(bucket.begin(),bucket.end());
}
vector<int> result;
for(auto& bucket:buckets){
for(auto num:bucket){
result.push_back(num);
}
}
return result;
}
10.基数排序
基数排序(Radix Sort)是一种非比较型的排序算法,它通过逐位比较元素的每一位(从最低位到最高位)来实现排序。基数排序的核心思想是将整数按位数切割成不同的数字,然后按每个位数分别进行排序。基数排序的时间复杂度为 O(n * k),其中 n 是列表长度,k 是最大数字的位数。
时间复杂度:O(n * k)
空间复杂度:O(n + k)
特点:当 k 较小时,性能优异,性能优异。
代码实现
cpp
vector<int> count_sort(vector<int>& nums,int exp){
int n = nums.size();
vector<int> result(n,0);
int count[10];
for(int num:nums){
int index = (num/exp)%10;
count[index]++;
}
for(int i=1;i<10;i++){
count[i]+=count[i-1];
}
for(int i = n-1;i>=0;i--){
int index = (nums[i]/exp)%10;
result[count[index]-1] = nums[i];
count[index]-=1;
}
return result;
}
更多详细内容可以参考1.0 十大经典排序算法 | 菜鸟教程