排序算法
快速排序(重点)
基本步骤
- 确定分界点
- 调整区间
- 递归处理
基本思想:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
注意边界问题,分界点和递归参数选取不当可能造成死循环
##时间复杂度
最优O(nlogn)\最差O(n2)
##空间复杂度
最优O(logn)\最差O(n)
具体复杂度的推导可以参考:快速排序的时间复杂度和空间复杂度分析(图文结合)-CSDN博客
#经典快排
#include<iostream>
using namespace std;
const int N = 1e6 + 10;
int q[N];
int n;
void quik_sort(int q[], int l,int r){
if(l>=r)
return;
int mid = q[(l+r)>>1],i=l-1,j=r+1;
while(i<j){
do i++;while(q[i]<mid);
do j--;while(q[j]>mid);
if(i<j)swap(q[i],q[j]);
}
quik_sort(q, l, j);
quik_sort(q, j+1, r);
}
int main(){
scanf("%d",&n);
for(int i = 0;i < n;i++)
scanf("%d",&q[i]);
quik_sort(q,0,n-1);
for(int i = 0;i < n;i++)
printf("%d ",q[i]);
return 0;
}
#第k个数
#include<iostream>
using namespace std;
const int N = 1e6+10;
int k,n;
int q[N];
int quik_sort(int l,int r,int k){
if(l>=r)
return q[l];
int mid = q[(l+r)>>1],i = l-1,j = r+1;
while(i<j){
do i++;while(q[i]<mid);
do j--;while(q[j]>mid);
if(i<j)swap(q[i],q[j]);
}
#选择对应区间
int num = j-l+1;
if(num>=k) return quik_sort(l,j,k);
else return quik_sort(j+1,r,k-num);
}
int main(){
scanf("%d",&n);
scanf("%d",&k);
for(int i = 0;i<n;i++)
scanf("%d",&q[i]);
printf("%d",quik_sort(0,n-1,k));
return 0;
}
归并排序(重点)
基本步骤
- 确定分界点,mid=(l+r)/2
- 递归排序left和right
- 归并,合二为一
基本思想:归并排序算法有两个基本的操作,一个是分,也就是把原数组划分成两个子数组的过程。另一个是治,它将两个有序数组合并成一个更大的有序数组。将待排序的线性表不断地切分成若干个子表,直到每个子表只包含一个元素,这时,可以认为只包含一个元素的子表是有序表。将子表两两合并,每合并一次,就会产生一个新的且更长的有序表,重复这一步骤,直到最后只剩下一个子表,这个子表就是排好序的线性表。
时间复杂度
O(nlogn)
空间复杂度
O(n),归并排序需要一个与原数组相同长度的数组做辅助来排序
参考文章:排序——归并排序(Merge sort)-CSDN博客
#经典归并排序
#include<iostream>
using namespace std;
const int N = 1e5+10;
int n;
int q[N],temp[N];
void merge_sort(int q[],int l ,int r){
if(l>=r)
return;
int mid = (l+r)>>1;
merge_sort(q,l,mid);
merge_sort(q,mid+1,r);
int num = 0,i = l,j = mid+1;
while(i<=mid&&j<=r){
if(q[i]<=q[j]) temp[num++]=q[i++];
else temp[num++]=q[j++];
}
while(i<=mid) temp[num++]=q[i++];
while(j<=r) temp[num++]=q[j++];
for(int i=l ,j = 0;i<=r;i++,j++) q[i]=temp[j];
}
int main(){
scanf("%d",&n);
for(int i = 0;i<n;i++)
scanf("%d",&q[i]);
merge_sort(q,0,n-1);
for(int i = 0 ;i<n;i++)
printf("%d ",q[i]);
return 0;
}
#逆序对数量
#include<iostream>
using namespace std;
typedef long long ll;
const int N = 1e5+10;
int n;
int q[N],temp[N];
ll merge_sort(int l,int r){
if(l>=r)
return 0;
int mid = (l+r)>>1;
ll num = 0;
num+=merge_sort(l,mid);
num+=merge_sort(mid+1,r);
int i = l,j = mid+1,k = 0;
while(i<=mid&&j<=r){
if(q[i]<=q[j]) temp[k++]=q[i++];
else{
temp[k++]=q[j++];
num+=mid-i+1;
}
}
while(i<=mid) temp[k++]=q[i++];
while(j<=r) temp[k++]=q[j++];
for(int i=l,j=0;i<=r;i++,j++) q[i]=temp[j];
return num;
}
int main(){
scanf("%d",&n);
for(int i = 0;i<n;i++)
scanf("%d",&q[i]);
printf("%lld",merge_sort(0,n-1));
return 0;
}
冒泡排序
算法步骤
冒泡排序(Bubble Sort)是一种简单直观的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢"浮"到数列的顶端。
时间复杂度
平均:O(n2),最好O(n),最坏O(n2)
空间复杂度
O(1)
#经典款冒泡排序
void bubble_sort(int arr[], int len) {
int i, j;
for (i = 0; i < len - 1; i++)
for (j = 0; j < len - 1 - i; j++)
if (arr[j] > arr[j + 1])
swap(arr[j], arr[j + 1]);
}
#增设判断的冒泡排序,不过感觉用处不大
void bubbleSort(int array[], int length)
{
int i, j, tmp;
int flag = 1;
if (1 >= length) return;
for (i = length-1; i > 0; i--, flag = 1){
for (j = 0; j < i; j++){
if (array[j] > array[j+1]){
tmp = array[j];
array[j] = array[j+1];
array[j+1] = tmp;
flag = 0;
}
}
if (flag) break;
}
}
#改进的冒泡排序(鸡尾酒排序)就是把最大的数往后面冒泡的同时,最小的数也往前面冒泡
void cocktailSort(int arr [] , int n) {
int L = 0;
while(L < n) {
for(int i = L; i < n-1; i++)
{
if(arr[i] > arr[i+1])
{
int temp = arr[i];
arr[i] = arr[i+1];
arr[i+1] = temp;
}
}
n--;
for(int i = n-1; i > L; i--)
{
if(arr[i] < arr[i-1])
{
int temp = arr[i];
arr[i] = arr[i-1];
arr[i-1] = temp;
}
}
L++;
}
}
选择排序
算法步骤
首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置。再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。重复第二步,直到所有元素均排序完毕。
时间复杂度
O(n2)
空间复杂度
O(1)
#经典选择排序
void Select_Sort(int arr[], int n)
{
for (int i = 0; i < n-1; i++) {
int min = i;
for (int j = i; j < n; j++) {
if (arr[min] > arr[j]) {
min = j;
}
}
if (min != i) {
swap(arr[i], arr[min]);
}
}
插入排序
算法步骤
插入排序是一种最简单直观的排序算法,它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。
详细资料:排序算法——直接插入排序(图文超详细!)-CSDN博客
时间复杂度
平均:O(n2),最好O(n),最坏O(n2)
空间复杂度
O(1)
#经典插入排序
void insert_sort(int arr[], int n) {
for (int i = 1; i < n; i++) {
int key = arr[i];
int j = i - 1;
while (j >= 0 && arr[j] > key) {
arr[j + 1] = arr[j];
j--;
}
arr[j + 1] = key;
}
}
希尔排序(插入排序的优化版本)
算法步骤
先选定一个整数,把待排序文件中所有记录分成多个组。所有距离为的记录分在同一组内,并对每一组内的记录进行直接插入排序。然后,取,重复上述分组和排序的工作。当到达=1时,所有记录在统一组内排好序。
详细资料:七大排序算法——希尔排序,通俗易懂的思路讲解与图解(完整Java代码)-CSDN博客
时间复杂度
平均:O(n1.3),最好O(n),最坏O(n2)
空间复杂度
O(1)
#经典希尔排序
void shell_sort(int arr[], int n) {
for (int gap = n / 2; gap > 0; gap /= 2) {
for (int i = gap; i < n; i++) {
int key = arr[i];
int j = i - gap;
while (j >= 0 && arr[j] > key) {
arr[j + gap] = arr[j];
j -= gap;
}
arr[j + gap] = key;
}
}
}
堆排序
算法步骤
1.首先将待排序的数组构造成一个大根堆,此时,整个数组的最大值就是堆结构的顶端
2.将顶端的数与末尾的数交换,此时,末尾的数为最大值,剩余待排序数组个数为n-1
3.将剩余的n-1个数再构造成大根堆,再将顶端数与n-1位置的数交换,如此反复执行,便能得到有序数组
详细讲解:【从堆的定义到优先队列、堆排序】 10分钟看懂必考的数据结构——堆_哔哩哔哩_bilibili
时间复杂度
平均:O(nlogn),最好O(nlogn),最坏O(nlogn)
空间复杂度
O(1)
#构建最大堆,升序
void heapify(int arr[], int n, int i) {
int largest = i;
int l = 2 * i + 1;
int r = 2 * i + 2;
if (l < n && arr[l] > arr[largest]) largest = l;
if (r < n && arr[r] > arr[largest]) largest = r;
if (largest != i) {
swap(arr[i], arr[largest]);
heapify(arr, n, largest);
}
}
void heap_sort(int arr[], int n) {
for (int i = n / 2 - 1; i >= 0; i--) {
heapify(arr, n, i);
}
for (int i = n - 1; i > 0; i--) {
swap(arr[0], arr[i]);
heapify(arr, i, 0);
}
}
计数排序
算法步骤:
- 找出待排序的数组中最大和最小的元素
- 统计数组中每个值为i的元素出现的次数,存入数组C的第i项
- 对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加)
- 反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减1
局限:
- 当数列最大最小值差距过大时,并不适用于计数排序
- 当数列元素不是整数时,并不适用于计数排序
计数排序是一个非基于比较的排序算法,该算法于1954年由 Harold H. Seward 提出它的优势在于在对一定范围内的整数排序时,它的复杂度为Ο(n+k)(其中k是整数的范围),快于任何比较排序算法。 当然这是一种牺牲空间换取时间的做法,而且当O(k)>O(nlog(n))的时候其效率反而不如基于比较的排序(基于比较的排序的时间复杂度在理论上的下限是O(nlog(n)), 如归并排序,堆排序)
详细参考:算法系列十一:十大经典排序算法之——计数排序-CSDN博客
时间复杂度
O (n+range)
空间复杂度
O(range)
#经典计数排序
void counting_sort(int arr[], int n) {
int max = *max_element(arr, arr + n);
int min = *min_element(arr, arr + n);
int range = max - min + 1;
int count[range] = {0};
int output[n];
for (int i = 0; i < n; i++) {
count[arr[i] - min]++;
}
for (int i = 1; i < range; i++) {
count[i] += count[i - 1];
}
for (int i = n - 1; i >= 0; i--) {
output[count[arr[i] - min] - 1] = arr[i];
count[arr[i] - min]--;
}
for (int i = 0; i < n; i++) {
arr[i] = output[i];
}
}
桶排序(计数排序的升级版)
算法步骤
参考:
时间复杂度
最好:O(n+k),最坏:所有数据都集中在一个桶中,桶排序退化为桶内排序算法的时间复杂度,例如O(n^2)
空间复杂度
O(n+k)
#经典桶排序
void bucket_sort(int arr[], int n) {
int max = *max_element(arr, arr + n);
int min = *min_element(arr, arr + n);
int range = max - min + 1;
int bucketCnt = 10;
vector<int> buckets[bucketCnt];
// 计算桶的大小
int bucketSize = 1;
while (max) {
max /= 10;
bucketSize *= 10;
}
bucketSize /= 10;
// 将元素分配到桶中
for (int i = 0; i < n; i++) {
int idx = arr[i] / bucketSize;
buckets[idx].push_back(arr[i]);
// 对每个桶进行插入排序
for (int j = int(buckets[idx].size()) - 1; j > 0; j--) {
if (buckets[idx][j] < buckets[idx][j - 1]) {
std::swap(buckets[idx][j], buckets[idx][j - 1]);
}
}
}
// 将桶中的元素合并到原数组中
for (int i = 0, k = 0; i < bucketCnt; i++) {
for (int j = 0; j < int(buckets[i].size()); j++) {
arr[k++] = buckets[i][j];
}
}
}
基数排序
算法步骤
参考:最详细的【基数排序】—排序算法,思路清晰动图保姆级讲解,五分钟搞懂!_csdn基数排序动图-CSDN博客
时间复杂度
O(K*n),k一般取10
空间复杂度
O(N+K)
#经典基数排序
void radix_sort(int arr[], int n) {
int max = *max_element(arr, arr + n);
int exp = 1;
int output[n];
while (max / exp > 0) {
int count[10] = {0};
for (int i = 0; i < n; i++) {
count[arr[i] / exp % 10]++;
}
for (int i = 1; i < 10; i++) {
count[i] += count[i - 1];
}
for (int i = n - 1; i >= 0; i--) {
output[count[arr[i] / exp % 10] - 1] = arr[i];
count[arr[i] / exp % 10]--;
}
for (int i = 0; i < n; i++) {
arr[i] = output[i];
}
exp *= 10;
}
}