排序算法作为算法和数据结构的重要部分,系统地学习一下是很有必要的。排序是计算机内经常进行的一种操作,其目的是将一组“无序”的记录序列调整为“有序”的记录序列。
排序分为内部排序和外部排序:
- 若整个排序过程不需要访问外存便能完成,则称此类排序问题为内部排序;
- 反之,若参加排序的记录数量很大,整个序列的排序过程不可能在内存中完成,则称此类排序问题为外部排序。
常说的的八大排序算法均属于内部排序。如果按照策略来分类,大致可分为:交换排序、插入排序、选择排序、归并排序和基数排序。如下图所示:
下表给出各种排序的基本性能,本篇文章不做详细介绍,具体分析请参看其他博客的详解:
1. 交换排序
1.1 冒泡排序
基本思想:比较相邻元素,每比较一轮,将最大的元素置于比较区间内的最后一个元素。优化点:如果哪一轮比较发现没有需要交换的元素,排序即结束。时间复杂度:平均和最坏情况--O(n的平方),最好情况:O(n),此时给定序列已经排好序。
void bubbleSort(vector<int>& nums) {
int n = nums.size();
bool needSwap = true;
for(int i = 1; i < n && needSwap; i++) {
bool needSwap = false;
for(int j = 0; j < n - i; j++) {
if(nums[j] > nums[j+1]) {
swap(nums[j], nums[j+1]);
needSwap = true;
}
}
}
}
1.2 快速排序
基本思想:保证区间内每个元素的所有左边元素都小于该元素,所有右边元素都大于该元素。时间复杂度平均情况为:O(nlogn),最坏情况:O(n的平方)(每次选的基准点的元素为最大元素或者最小元素),最好情况: O(nlogn)。
int partition(vector<int>& nums, int left, int right) {
int pivotValue = nums[left];
while(left < right) {
while(left < right && nums[right] >= pivotValue)
right--;
nums[left] = nums[right];
while(left < right && nums[left] <= pivotValue)
left++;
nums[right] = nums[left];
}
nums[left] = pivotValue;
return left;
}
void quickSort(vector<int>& nums, int left, int right) {
if(left < right) {
int pivotInd = partition(nums, left, right);
quickSort(nums, left, pivotInd - 1);
quickSort(nums, pivotInd + 1, right);
}
}
2. 插入排序
2.1 直接插入排序
基本思想:处理下标为i的元素时,0~i-1的元素已经排好序,需要找到下标为i元素的插入位置。时间复杂度平均情况为:O(n的平方),最坏情况:O(n的平方),最好情况: O(n),此时给定序列已经排好序。
void insertSort(vector<int>& nums) {
int n = nums.size();
int i = 0, j = 0;
for(i = 1; i < n; i++) {
int temp = nums[i];
for(j = i - 1; j >= 0; j--) {
if(nums[j] > temp) {
nums[j + 1] = nums[j];
} else {
break;
}
}
nums[j+1] = temp;
}
}
2.2 希尔排序
基本思想:不断缩小插入排序的步长,直到为0时退出,排序结束。时间复杂度平均情况为:O(nlogn),最坏情况:O(n的平方),最好情况: 未知,大概n的1.3次方左右。
void shellShort(vector<int>& nums) {
int n = nums.size();
int gap = n / 2;
int i = 0, j = 0;
while(gap > 0) {
for(i = gap; i < n; i++) {
int temp = nums[i];
for(j = i - gap; j >= 0; j = j - gap) {
if(nums[j] > temp) {
nums[j + gap] = nums[j];
} else {
break;
}
}
nums[j + gap] = temp;
}
gap = gap / 2;
}
}
3. 选择排序
3.1 简单选择排序
基本思想:每次选中排序区间内的最小元素(记录其下标),置于排序区间的第一个位置。其平均、最好,最坏时间复杂度均为O(n的平方)。
void simpleSelectSort(vector<int>& nums) {
int n = nums.size();
for(int i = 0; i < n; i++) {
int minInd = i;
for(int j = i + 1; j < n; j++) {
if(nums[j] < nums[i])
minInd = j;
}
if(minInd != i) {
swap(nums[i], nums[minInd]);
}
}
}
3.2 堆排序
基本思想:将数组视为一颗二叉树,下标为i的元素,其左右树的下标分别为2*i+1, 2*i+2(如果存在的话)。每次取出堆顶元素,进行堆的调整,如果往复,直到堆的大小为1,直接取出堆顶元素即可。其平均、最好,最坏时间复杂度均为O(nlogn)。
- 如果是升序排序,则采用大顶堆,每次取出当前待排元素中的最大值,将其与最后一个位置的元素进行交换。
- 如果是降序排序,则采用小顶堆,每次取出当前待排元素中的最小值,将其与最后一个位置的元素进行交换。
void headAjust(vector<int>& nums, int parent, int heapSize) {
int left = 2 * parent + 1;
int right = 2 * parent + 2;
int largestInd = parent;
if(left < heapSize && nums[left] > nums[largestInd])
largestInd = left;
if(right < heapSize && nums[right] > nums[largestInd])
largestInd = right;
if(largestInd != parent) {
swap(nums[parent], nums[largestInd]);
headAjust(nums, largestInd, heapSize);
}
}
void heapSort(vector<int>& nums) {
int n = nums.size();
for(int i = n / 2 - 1; i >= 0; i--) {
headAjust(nums, i, n);
}
for(int i = n; i > 0; i--) {
headAjust(nums, 0, i);
swap(nums[0], nums[i - 1]);
}
}
4. 归并排序
基本思想:采用分治的思想,先分成很小的子序列,然后依次合并(两路),最后得到排序数组。其平均、最好,最坏时间复杂度均为O(nlogn)。
void merge(vector<int>& nums, int left, int right) {
int mid = (left + right) / 2;
int *temp = new int[right - left + 1];
int i = left, j = mid + 1, k = 0;
while(i <= mid && j <= right) {
if(nums[i] < nums[j]) {
temp[k++] = nums[i];
i++;
} else {
temp[k++] = nums[j];
j++;
}
}
while(i <= mid)
temp[k++] = nums[i++];
while(j <= right)
temp[k++] = nums[j++];
for(int i = left; i <= right; i++) {
nums[i] = temp[i - left];
}
delete []temp;
}
void mergeSort(vector<int>& nums, int left, int right) {
if(left < right) {
int mid = (left + right) / 2;
mergeSort(nums, left, mid);
mergeSort(nums, mid+1, right);
merge(nums, left, right);
}
}
5. 基数排序
基本思想:
(1)找出待排序的数组中最大和最小的元素
(2)统计数组中每个值为i的元素出现的次数,存入数组的第i项
(3)对所有的计数累加(从数组中的第一个元素开始,每一项和前一项相加)
(4)反向填充目标数组:将每个元素i放在新数组的第(i)项,每放一个元素就将(i)减去1
void countSort(vector<int>& nums)
{
int n = nums.size();
if(n <= 1) return;
//确定数组最大值和最小值
int max = nums[0], min = nums[0];
for (int i = 1; i < n; i++)
{ if (nums[i] > max)
max = nums[i];
if (nums[i] < min)
min = nums[i];
}
int len = max - min + 1;
// 确定统计数组长度并进行初始化
int* count = new int[len];
for (int i = 0; i <= len; ++i)
count[i] = 0;
// 遍历数组,统计每个数出现的次数
for (int i = 0; i < n; ++i)
++count[nums[i] - min];
// 统计数组做变形,后面的元素个数等于前面的元素个数之和, 再加上自身元素的个数
for (int i = 1; i <= len; ++i)
count[i] += count[i - 1];
// 倒序遍历原始数列,从统计数组找到正确的位置,输出到结果数组
int* sortedArray = new int[n];
for (int i = n - 1; i >= 0; i--)
{
sortedArray[count[nums[i] - min] - 1] = nums[i]; // 找到nums[i]对应的count的值,值为多少,表示原来排序多少,(因为从1开始,所以再减1)
count[nums[i] - min]--; // 然后将相应的count的值减1,表示下次再遇到此值时,原来的排序是多少。
}
for(int i = 0; i < n; i++) {
nums[i] = sortedArray[i];
}
}
6. 参考资料
- https://blog.csdn.net/pushup8/article/details/85196465
- https://blog.csdn.net/weixin_41872403/article/details/87872448
- https://blog.csdn.net/zy704599894/article/details/79581238?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2.nonecase&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2.nonecase
- https://blog.csdn.net/qq_41855420/article/details/93653175
- https://www.cnblogs.com/wyr-123-wky/p/11093919.html