十大排序C++版

冒泡排序

通过连续地比较与交换相邻元素实现排序,每次最大或最小的元素像冒泡一样被排好。
特性:

  • 最差时间复杂度和平均时间复杂度为 O(n2),当输入数组完全有序时,可达到最佳时间复杂度O(n)。
  • 空间复杂度为O(1),原地排序
  • 稳定排序,自适应排序
void bubbleSort(vector<int>& nums) {
	int n = nums.size();
	for (int i = 0; i < n - 1; i++) {
		bool flag = false; // 标志是否发生了交换,没发生则已经有序,就提前退出
		for (int j = 0; j < n - 1 - i; j++) {
			if (nums[j] > nums[j + 1]) {
				std::swap(nums[j], nums[j+1]);
				flag = true;
			}
		}
		if(!flag) break;
	}
}

快速排序

快速排序可看成冒泡排序升级版,不是交换相邻元素,而是通过分治策略,前后交换,每次将一个元素放到正确位置。快速排序可看成二叉树的前序遍历
特性:

  • 时间复杂度为O( n log ⁡ n n \log_{} n nlogn),最坏情况是 O(n2)
  • 空间复杂度为O(n):完全倒序的情况下,达到最差递归深度n
  • 非稳定排序,自适应排序
void swap(vector<int>& nums, int i, int j) {
	int temp = nums[i];
	nums[i] = nums[j];
	nums[j] = temp;
}
// 随机打乱,避免极端情况
void shuffle(vector<int>& nums) {
     srand((unsigned) time(NULL));
     int n = nums.size();
     for (int i = 0 ; i < n; i++) {
        // 生成 [i, n - 1] 的随机数
        int r = i + rand()%(n-i);
        swap(nums,i,r);
     }
}
int partion(vector<int>& nums, int low, int high) {
	// 以 nums[low] 为基准数
	int pivot = nums[low];
	int i = low, j = high;
	while (i < j) {
		while (i < j && nums[j] > pivot) j--; // 从后向前找第一个小于基准的数
		while (i < j && nums[i] <= pivot) i++; // 从前往后找第一个大于基准的数
		// =必须放在这里,因为i最开始等于low,所以最开始pivot=nums[i],有=才能执行i++跳过
		swap(nums, i, j);
	}
	swap(nums, low, i); // 将基准数交换至两子数组的分界线
	return i; // 返回基准数的索引
}
void quickSort(vector<int>& nums, int low, int high) {
	if (low >= high) return;
	int p = partion(nums, low, high);
	quickSort(nums, low, p - 1);
	quickSort(nums, p + 1, high);
}
void quickSort(vector<int>& nums) {
	shuffle(nums);
	quickSort(nums, 0, nums.size() - 1);
}

partion的第二个版本:

int partiton(vector<int>& nums,int low, int high){
    int pivot=nums[low];
    // 这里把 i, j 定义为开区间,同时定义:
    // [lo, i) <= pivot;(j, hi] > pivot
    int i=low+1, j=high;
    // 当 i > j 时结束循环,以保证区间 [lo, hi] 都被覆盖
    while(i<=j){
        while(i<high && nums[i]<=pivot) i++;  // 此 while 结束时恰好 nums[i] > pivot,从前往后找第一个比基准大的
        while(j>low && nums[j]>pivot) j--;    // 此 while 结束时恰好 nums[j] <= pivot,从后往前找第一个比基准小的
        if(i>=j) break;
        // 此时 [lo, i) <= pivot && (j, hi] > pivot
        // 交换 nums[j] 和 nums[i]
        swap(nums,i,j);
        // 此时 [lo, i] <= pivot && [j, hi] > pivot
    }
    // 最后将 pivot 放到合适的位置,即 pivot 左边元素较小,右边元素较大
    swap(nums,low,j);
    return j;
}

插入排序

从未排序序列中选一个元素,放到已排序序列的正确位置。
特性:

  • 平均时间复杂度为 O(n2),当输入数组完全有序时,可达到最佳时间复杂度O(n)。尽管插入排序的时间复杂度更高,但在数据量较小的情况下,插入排序通常更快。
  • 空间复杂度为O(1),原地排序
  • 稳定排序,自适应排序
void insertSort(vector<int>& nums) {
	int n = nums.size();
	for (int i = 1; i < n; i++) { // 默认第一个元素有序,所以从i=1开始
		int preIndex = i - 1;
		int cur = nums[i];		// 当前要插入到有序序列中的元素
		while (preIndex >= 0 && nums[preIndex] > cur) {
			nums[preIndex + 1] = nums[preIndex];
			preIndex--;
		}
		nums[preIndex+1] = cur; // preIndex已经减了1,这里就要+1
	}
}

希尔排序

希尔排序可以看成插入排序的升级版,交换不相邻的元素以对数组的局部进行排
序。先让有一定间隔之间的元素有序,再缩小间隔,相当于分成了多个逻辑组,每个组排序。
特性:

  • 希尔排序时间复杂度计算非常复杂,和增量有关,时间复杂度最坏为O(n2)
  • 空间复杂度为O(1)
  • 非稳定排序
void shellSort(vector<int>& nums) {
	int n = nums.size();
	int gap = 1;
	while (gap < n / 3) gap = gap * 3 + 1;
	while (gap >= 1) {
		for (int i = gap; i < n; i++) {
			for (int j = i; j >= gap && nums[j] < nums[j - gap]; j -= gap) std::swap(nums[j], nums[j - gap]);
		}
		gap /= 3;
	}
}

选择排序

每次从未排序区间找最大或最小的放在开头或末尾。
特性:

  • 时间复杂度为 O(n2),
  • 空间复杂度为O(1),原地排序
  • 非稳定排序,非自适应排序
void selectionSort(vector<int>& nums) {
	int n = nums.size();
	for (int i = 0; i < n; i++) {
		int minIndex = i;
		for (int j = i + 1; j < n; j++) { // 寻找未排序中最小的数
			if (nums[j] < nums[minIndex]) {
				minIndex = j; // 记录最小元素的索引
			}
		}
		std::swap(nums[i], nums[minIndex]);
	}
}

堆排序

堆排序可以看成选择排序的升级版,也是每次将最大或最小的元素选出来放到末尾,但借用了二叉堆的数据结构,提升了效率。
特性:

  • 时间复杂度为O( n log ⁡ n n \log_{} n nlogn)
  • 空间复杂度为O(1),原地排序
  • 非稳定排序,非自适应排序
// 堆的长度为 n ,从节点 i 开始,从顶至底堆化
void siftDown(vector<int>& nums, int n, int i) {
	while (true) {
		int left = i * 2 + 1;
		int right = i * 2 + 2;
		int maxIndex = i;
		if (left<n && nums[left]>nums[maxIndex]) maxIndex = left;
		if (right<n && nums[right]>nums[maxIndex]) maxIndex = right;
		// 若节点 i 最大或索引 l, r 越界,则无须继续堆化,跳出
		if(i==maxIndex) break; 
		std::swap(nums[maxIndex], nums[i]);
		i = maxIndex;
	}
}
void heapSort(vector<int>& nums) {
	int n = nums.size();
	// 建堆操作:堆化除叶节点以外的其他所有节点
	for (int i = n / 2 - 1; i >= 0; i--) siftDown(nums, n, i);
	// 从堆中提取最大元素,循环 n-1 轮
	for (int i = n - 1; i > 0; i--) {
		// 交换根节点与最右叶节点(交换首元素与尾元素)
		std::swap(nums[0], nums[i]); 
		// 交换后堆化
		siftDown(nums, i, 0);
	}
}

归并排序

归并排序可看成二叉树的后序遍历。先划分为左右子数组,对子数组排序后再合并。
特性:

  • 时间复杂度为O( n log ⁡ n n \log_{} n nlogn)
  • 空间复杂度为O(n)
  • 稳定排序,非自适应排序
void merge(vector<int>& nums, int left, int mid, int right) {
	// 左子数组区间为 [left, mid], 右子数组区间为 [mid+1, right]
	// 创建一个临时数组 tmp ,用于存放合并后的结果
	// 可以在外面创建辅助数组,避免递归时频繁创建
	vector<int> temp(right - left + 1);
	int i = left, j = mid + 1;
	int k = 0;
	while (i <= mid && j <= right) {
		if (nums[i] <= nums[j]) temp[k++] = nums[i++];
		else temp[k++] = nums[j++];
	}
	// 将左子数组和右子数组的剩余元素复制到临时数组中
	while (i <= mid) temp[k++] = nums[i++];
	while (j <= right) temp[k++] = nums[j++];
	// 将临时数组 tmp 中的元素复制回原数组 nums 的对应区间
	for (k = 0; k < temp.size(); k++) nums[left+k] = temp[k];
}
void mergeSort(vector<int>& nums, int left, int right) {
	// 当子数组长度为 1 时终止递归
	if (left >= right) return; 
	int mid = (left + right) / 2;
	mergeSort(nums, left, mid); 	 // 递归左子数组
	mergeSort(nums, mid + 1, right); // 递归右子数组
	merge(nums, left, mid, right);   // 合并
}
void mergeSort(vector<int>& nums) {
	mergeSort(nums, 0, nums.size() - 1);
}

计数排序

利用一个计数数组的天然有序索引排序,只适用于非负整数。
特性:

  • 时间复杂度为O(m+n)
  • 空间复杂度为O(m)
  • 正序遍历非稳定,倒序遍历稳定
  • 计数排序不是比 排序,排序的速度快于任何比较排序算法
void countSort(vector<int>& nums) {
	int maxVal = nums[0];
	// 统计数组最大元素
	for (int num : nums) maxVal = max(maxVal, num);
	vector<int> counter(maxVal + 1, 0);
	// 统计各数字的出现次数
	for (int num : nums) counter[num]++;
	int idx = 0;
	// 遍历 counter ,将各元素填入原数组 nums
	for (int i = 0; i < counter.size(); i++) {
		while (counter[i] > 0) {
			nums[idx++] = i;
			counter[i]--;
		}
	}
}

桶排序

桶排序可以看作计数排序的升级版。设置一些具有大小顺序的桶,每个桶对应一个数据范围,将数据平均分配到各个桶中;然后,在每个桶内部分别执行排序;最终按照桶的顺序将所有数据合并。
特性:

  • 时间复杂度为O(n+k)
  • 空间复杂度为O(n+k)
  • 是否稳定取决于排序桶内元素的算法是否稳定,自适应排序
const int BUCKET_NUM = 10;

struct ListNode {
	explicit ListNode(int i = 0) :mData(i), mNext(NULL) {}
	ListNode* mNext;
	int mData;
};

ListNode* insert(ListNode* head, int val) {
	ListNode dummyNode;
	ListNode* newNode = new ListNode(val);
	ListNode* pre, * curr;
	dummyNode.mNext = head;
	pre = &dummyNode;
	curr = head;
	while (NULL != curr && curr->mData <= val) {
		pre = curr;
		curr = curr->mNext;
	}
	newNode->mNext = curr;
	pre->mNext = newNode;
	return dummyNode.mNext;
}


ListNode* Merge(ListNode* head1, ListNode* head2) {
	ListNode dummyNode;
	ListNode* dummy = &dummyNode;
	while (NULL != head1 && NULL != head2) {
		if (head1->mData <= head2->mData) {
			dummy->mNext = head1;
			head1 = head1->mNext;
		}
		else {
			dummy->mNext = head2;
			head2 = head2->mNext;
		}
		dummy = dummy->mNext;
	}
	if (NULL != head1) dummy->mNext = head1;
	if (NULL != head2) dummy->mNext = head2;

	return dummyNode.mNext;
}

void bucketSort(vector<int>& nums) {
	int n = nums.size();
	vector<ListNode*> buckets(BUCKET_NUM, (ListNode*)(0));
	for (int i = 0; i < n; ++i) {
		int index = nums[i] / BUCKET_NUM;
		ListNode* head = buckets.at(index);
		buckets.at(index) = insert(head, nums[i]);
	}
	ListNode* head = buckets.at(0);
	for (int i = 1; i < BUCKET_NUM; ++i) {
		head = Merge(head, buckets.at(i));
	}
	for (int i = 0; i < n; ++i) {
		nums[i] = head->mData;
		head = head->mNext;
	}
}

基数排序

思想与计数排序一致,也通过统计个数来实现排序。一种多关键字的排序算法。将整数按位数切割成不同的数字,然后按每个位数分别比较。由于整数也可以表达字符串和特定格式的浮点数,所以基数排序也不是只能使用于整数。
特性:

  • 时间复杂度为O(nk),最大位数为k, 一般n和k 都相对较小,时间复杂度趋向O(n)。
  • 空间复杂度为O(n+d),数据为d进制
  • 用的计数排序稳定则稳定,否则不一定稳定,非自适应排序。
int maxbit(int data[], int n) //辅助函数,求数据的最大位数
{
    int maxData = data[0];              ///< 最大数
    /// 先求出最大数,再求其位数,这样有原先依次每个数判断其位数,稍微优化点。
    for (int i = 1; i < n; ++i)
    {
        if (maxData < data[i])
            maxData = data[i];
    }
    int d = 1;
    int p = 10;
    while (maxData >= p)
    {
        //p *= 10; // Maybe overflow
        maxData /= 10;
        ++d;
    }
    return d;
/*    int d = 1; //保存最大的位数
    int p = 10;
    for(int i = 0; i < n; ++i)
    {
        while(data[i] >= p)
        {
            p *= 10;
            ++d;
        }
    }
    return d;*/
}
void radixsort(int data[], int n) //基数排序
{
    int d = maxbit(data, n);
    int *tmp = new int[n];
    int *count = new int[10]; //计数器
    int i, j, k;
    int radix = 1;
    for(i = 1; i <= d; i++) //进行d次排序
    {
        for(j = 0; j < 10; j++)
            count[j] = 0; //每次分配前清空计数器
        for(j = 0; j < n; j++)
        {
            k = (data[j] / radix) % 10; //统计每个桶中的记录数
            count[k]++;
        }
        for(j = 1; j < 10; j++)
            count[j] = count[j - 1] + count[j]; //将tmp中的位置依次分配给每个桶
        for(j = n - 1; j >= 0; j--) //将所有桶中记录依次收集到tmp中
        {
            k = (data[j] / radix) % 10;
            tmp[count[k] - 1] = data[j];
            count[k]--;
        }
        for(j = 0; j < n; j++) //将临时数组的内容复制到data中
            data[j] = tmp[j];
        radix = radix * 10;
    }
    delete []tmp;
    delete []count;
}

总结

在这里插入图片描述
图片来自https://www.runoob.com/w3cnote/ten-sorting-algorithm.html

参考:
https://www.hello-algo.com/chapter_sorting/sorting_algorithm/
https://www.runoob.com/w3cnote/ten-sorting-algorithm.html

  • 16
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值