几种常见的排序算法
一、插入排序
插入排序算法的原理类似于我们平时玩斗地主的时候需要摸牌并且在手上排序,一般来说我们会将摸到的纸牌放到左手上排序,左手上的牌全都是已经排好序的,右手摸到的牌是待插入的纸牌。
源码
#include<iostream>
using namespace std;
int main()
{
int n,i;
cin >> n;
int *arr = new int[n];
for (i = 0; i < n; i++)cin >> arr[i];
//输入数组
int key,j;
for (j = 1; j < n; j++) {
key = arr[j];//key是待插入的数字
i = j - 1;
while (i >= 0 && arr[i]>key) {
arr[i + 1] = arr[i];
i = i - 1;
}
//往前查找比key小的数字,保证前面的数都是已经排好序的
arr[i + 1] = key;
}
for (i = 0; i < n; i++) {
cout << arr[i] << " ";
}
cout << endl;
//输出数组
return 0;
}
源码说明:
arr[0]到arr[j-1]类比于左手上的牌,都是已经排好序的,key则是右手上的待插入的牌,而其余的则类比于放在桌上待摸的牌,也就是没有经过排序的。
for循环类似右手摸牌(数)的过程,每次取得一个待插入的数存放到 key 中,while循环是将左手上的数从右往左(即从大到小)和 key 进行比较,举个栗子,现在我左手上的牌是2 3 5 6,右手上的牌是4,现在我和6进行比较,4比6小,那么我把6往后移动一个位置,变成2 3 5 _ 6,再把4和5比较,4还是比5小,于是我再把5向后移动一个位置,变成2 3 _ 5 6,4再和3比较,bingo,终于比3大了,于是4便放到3和5中间的位置了
二、归并排序
归并排序利用的是分治法的思想,即把一个原问题分解成若干个原理相同但规模较小的子问题,递归求解这些子问题,最后再合并这些子问题的解最终得到原问题的解。
分治步骤(分解——解决——合并)
源码:
#include<iostream>
using namespace std;
void merge(int*arr, int p, int q,int r);//合并排好序的两个数组
void mergeSort(int *arr, int p, int r);//分解数组
int main(){
int n, i;
cin >> n;
int *arr = new int[n];
for (i = 0; i < n; i++)cin >> arr[i];
//输入数组
mergeSort(arr, 0, n - 1);//分解数组
for (i = 0; i < n; i++) {
cout << arr[i] << " ";
}
cout << endl;
//输出数组
}
void merge(int*arr, int p, int q,int r) {
int i, j;
int n1 = q - p + 1;
int n2 = r - q;
int *left_arr = new int[n1+1];
int *right_arr = new int[n2+1];
for (i = 0; i < n1; i++) {
left_arr[i] = arr[p + i];
}
for (j = 0; j < n2; j++) {
right_arr[j] = arr[q + j + 1];
}
//将待归并的数组部分分成两个部分,左边为left_arr,右边为right_arr,两边都已经排好序
left_arr[n1] = INT_MAX;
right_arr[n2] = INT_MAX;
//设置哨兵牌
int k;
for (k = p,i=0,j=0; k <= r; k++) {
if (left_arr[i] <= right_arr[j]) {
arr[k] = left_arr[i];
i++;
}
else {
arr[k] = right_arr[j];
j++;
}
}
//将left_arr和right_arr按从小到大的顺序再复制回原数组arr
}
void mergeSort(int *arr, int p, int r) {
if (p < r) {
mergeSort(arr, p, (p + r) / 2);//将分解后的数组的左边再分解
mergeSort(arr, (p + r) / 2 + 1, r);//将分解后的数组右边再分解
merge(arr, p, (p + r) / 2, r);//开始归并位置索引为p到r的数组元素
}
}
源码分析:首先将数组arr通过函数 mergeSort 不断递归分解成两个等长的子数组,直至每个子数组只含一个元素,这时候开始进行归并,执行 merge 函数:将arr中索引位置为p到r的等分复制到left_arr和right_arr中,再给left_arr和right_arr最后添加上一个哨兵牌,即INT_MAX(int型的任何数都不可能大于INT_MAX),然后left_arr和right_arr一个一个开始进行比较,哪个比较小就填会arr。
下面附上算法原理图(via 算法导论):
算法时间复杂度:
nlg(n)
n
l
g
(
n
)
(这里的lg代表
log2
l
o
g
2
),上图中树的高度为
lgn+1
l
g
n
+
1
,每层的计算代价为cn,则
cn×(lgn+1)
c
n
×
(
l
g
n
+
1
)
,取其增长量级,故算法的时间复杂度为
O(nlgn)
O
(
n
l
g
n
)
三、冒泡排序
冒泡排序通过反复交换相邻的未按次序排列的元素,大的元素往下沉,小的元素往上浮,美其名曰:冒泡法。时间复杂度O(
n2
n
2
)
源码:
#include<iostream>
using namespace std;
void bubbleSort(int *arr,int n);
int main(){
int n, i;
cin >> n;
int *arr = new int[n];
for (i = 0; i < n; i++)cin >> arr[i];
//输入数组
bubbleSort(arr,n);
for (i = 0; i < n; i++) {
cout << arr[i] << " ";
}
cout << endl;
//输出数组
}
void bubbleSort(int *arr,int n) {
int i, j, temp;
for (i = 0; i < n - 1; i++) {
for (j = 0; j < n-1; j++) {
if (arr[j] > arr[j + 1]) {
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
四、选择排序
选择排序通过找出数组arr中最小的元素并与arr[0]进行替换,再找出arr中第二小的元素并与arr[1]进行替换….以此类推,实现排序
源码:
#include<iostream>
using namespace std;
int main(){
int n, i,j;
cin >> n;
int *arr = new int[n];
for (i = 0; i < n; i++)cin >> arr[i];
//输入数组
int key, index;//key用来存放当前最小值,index为当前最小值的索引下标
for (i = 0; i < n - 1; i++) {
key = arr[i];//把待替换的数组元素及其索引先存放进key和index
index = i;
for (j = i; j < n; j++) {
if (key > arr[j]) {//若后续的值比key小,则更新key值和index值
key = arr[j];
index = j;
}
}
arr[index] = arr[i];//进行两个数组元素的替换
arr[i] = key;
}
for (i = 0; i < n; i++) {
cout << arr[i] << " ";
}
cout << endl;
//输出数组
}
五、堆排序
堆排序通过三个函数来实现排序。第一个是 maintain_max_heap 主要是用来维护最大堆;第二个是 build_heap 用来建堆,该函数通过对非叶子结点调用 maintain_max_heap 来实现;第三个是 heap_sort ,该函数先调用 build_heap 建堆,然后使堆的每个最后叶子节点和根节点互换并使用 maintain_max_heap 来维护最大堆。
最大堆:
满足每个节点的值
≥
≥
子节点的值 的堆,可用数组来实现
维护最大堆:
对每个非叶子节点调用 maintain_max_heap
建堆:
heap_sort:每次从堆取出根节点的值(最大值),并对剩余堆进行维护
源码:
#include<iostream>
using namespace std;
void maintain_max_heap(int *arr, int node_index,int length);
void heap_sort(int *arr,int length);
void build_max_heap(int *arr,int length);
int main()
{
int length;
cout << "请输入数组长度:" << endl;
cin >> length;
int *arr = new int[length];
for (int i = 0; i < length; i++)
{
cin >> arr[i];
}
heap_sort(arr,length);
for (int i = 0; i < length; i++)
{
cout << arr[i] << " ";
}
}
//堆排序
void heap_sort(int *arr,int length)
{
//建堆
build_max_heap(arr, length);
//将堆中最大索引指向的值与arr[0]互换,将堆大小减去1,并对堆使用maintain_max_heap进行维护
int temp;
for (int heap_size = length - 1; heap_size > 0; heap_size--)
{
temp = arr[0];
arr[0] = arr[heap_size];
arr[heap_size] = temp;
maintain_max_heap(arr, 0, heap_size);
}
}
//维护最大堆
void maintain_max_heap(int *arr, int node_index, int length)
{
int left_node_index = (node_index + 1) * 2 - 1;
int right_node_index = (node_index + 1) * 2;
int largest_node_index;
//选择左子节点和右子节点比该节点大且为最大值的节点交换值
if (left_node_index<length&&arr[left_node_index]>arr[node_index])
largest_node_index = left_node_index;
else
largest_node_index = node_index;
if (right_node_index<length&&arr[right_node_index]>arr[largest_node_index])
largest_node_index = right_node_index;
//若有交换则递归调用maintain_max_heap函数
int temp;
if (largest_node_index != node_index)
{
temp = arr[node_index];
arr[node_index] = arr[largest_node_index];
arr[largest_node_index] = temp;
maintain_max_heap(arr, largest_node_index, length);
}
}
//建堆
void build_max_heap(int *arr, int length)
{
//(length-1)/2到0为非叶子节点的索引,此循环用来对非叶子节点循环调用maintain_max_heap函数
for (int node_index = (length - 1) / 2; node_index >= 0; node_index--)
{
maintain_max_heap(arr, node_index, length);
}
}
时间复杂度为O(nlgn)
六、快速排序
快速排序的算法原理:
算法的难点在于划分数组的准绳——主元 如何选择才能使数组左右保持平衡
源码:
#include<iostream>
using namespace std;
void quick_sort(int *arr, int head, int tail);
int partition(int *arr, int head, int tail);
void exchange(int *a, int *b);
int main()
{
int n;
cout << "请输入数组大小" << endl;
cin >> n;
int *arr = new int[n];
cout << "请输入数组" << endl;
for (int i = 0; i < n; i++)
cin >> arr[i];
quick_sort(arr, 0, n - 1);
cout << "排序结果" << endl;
for (int i = 0; i < n; i++)
cout << arr[i] << " ";
}
//快速排序
void quick_sort(int *arr, int head, int tail)
{
int q;
if (tail > head)
{
q = partition(arr, head, tail);
quick_sort(arr, head, q - 1);
quick_sort(arr, q + 1, tail);
}
}
//该函数用来选出一个主元并返回,并以它为依据来划分数组,左半段为小于主元的数,右半段为大于主元的数
//该函数为快速排序的核心
int partition(int *arr, int head, int tail)
{
int i = head - 1;
int j, x;
//选出arr[tail]为主元,并以它为准绳进行划分
for (j = head, x = arr[tail]; j < tail; j++)
{
if (arr[j] < x)
{
i = i + 1;
exchange(&arr[j], &arr[i]);
}
}
//将主元和索引为i+1的数进行交换
exchange(&arr[i + 1], &arr[tail]);
return i + 1;
}
//交换函数,用来交换两个数的值
void exchange(int *a, int *b)
{
int temp;
temp = *a;
*a = *b;
*b = temp;
}
七、快速排序随机化版本
快速排序的随机化版本基本原理和快速排序的普通版相同,只不过主元是随机选出来的
源码:
#include<iostream>
#include<ctime>
using namespace std;
void randomized_quick_sort(int *arr, int head, int tail);
int randomized_partition(int *arr, int head, int tail);
int partition(int *arr, int head, int tail);
void exchange(int *a, int *b);
int main()
{
int n;
cout << "请输入数组大小" << endl;
cin >> n;
int *arr = new int[n];
cout << "请输入数组" << endl;
for (int i = 0; i < n; i++)
cin >> arr[i];
randomized_quick_sort(arr, 0, n - 1);
cout << "排序结果" << endl;
for (int i = 0; i < n; i++)
cout << arr[i] << " ";
}
//快速排序
void randomized_quick_sort(int *arr, int head, int tail)
{
int q;
if (tail > head)
{
q = randomized_partition(arr, head, tail);
randomized_quick_sort(arr, head, q - 1);
randomized_quick_sort(arr, q + 1, tail);
}
}
//随机产生主元
int randomized_partition(int *arr, int head, int tail)
{
int random_index;
srand((unsigned)time(NULL));
random_index = rand() % (tail - head + 1) + head;
exchange(&arr[random_index], &arr[tail]);
return partition(arr, head, tail);
}
//该函数用来选出一个主元并返回,并以它为依据来划分数组,左半段为小于主元的数,右半段为大于主元的数
//该函数为快速排序的核心
int partition(int *arr, int head, int tail)
{
int i = head - 1;
int j, x;
//选出arr[tail]为主元,并以它为准绳进行划分
for (j = head, x = arr[tail]; j < tail; j++)
{
if (arr[j] < x)
{
i = i + 1;
exchange(&arr[j], &arr[i]);
}
}
//将主元和索引为i+1的数进行交换
exchange(&arr[i + 1], &arr[tail]);
return i + 1;
}
//交换函数,用来交换两个数的值
void exchange(int *a, int *b)
{
int temp;
temp = *a;
*a = *b;
*b = temp;
}
总结
以上的排序算法都属于比较排序,比较排序可以抽象为一棵决策树
八、计数排序
计数排序的时间复杂度为O(n),但只适合于有一定范围的非负整数
#include<iostream>
#include<cstring>
using namespace std;
void CountingSort(int *input_arr,int *output_arr,int length);
int main()
{
int length;
cout << "**计数排序中数组的元素必须为有一定范围的非负正整数**" << endl;
cout << "请输入数组大小" << endl;
cin >> length;
int *input_arr = new int[length];
int *output_arr = new int[length];
cout << "请输入数组" << endl;
for (int i = 0; i < length; i++)
{
cin >> input_arr[i];
}
CountingSort(input_arr, output_arr, length);
cout << "计数排序结果为" << endl;
for (int i = 0; i < length; i++)
{
cout << output_arr[i] << " ";
}
}
//计数排序
void CountingSort(int *input_arr, int *output_arr, int length)
{
//寻找数组中的最大值
int max = INT_MIN;
for (int i = 0; i < length; i++)
{
if (input_arr[i] > max)
max = input_arr[i];
}
int *temp_arr = new int[max+1];
//给中间数组赋值为0
for (int i = 0; i < max + 1; i++)
{
temp_arr[i] = 0;
}
//将input_arr的值对应到temp_arr的索引;若input_arr的某个值为i,则temp_arr[i]对应的值+1
for (int i = 0; i < length; i++)
{
temp_arr[input_arr[i]] += 1;
}
//累加,该循环执行后temp_arr的值-1就是对应的数在output_arr中的位置
for (int i = 1; i < max + 1; i++)
{
temp_arr[i] += temp_arr[i - 1];
}
//对output_arr进行赋值
for (int i = length - 1; i >= 0; i--)
{
//这里和算法导论不同,需要-1,因为算法导论中的数组索引是从1开始的
output_arr[temp_arr[input_arr[i]]-1] = input_arr[i];
temp_arr[input_arr[i]] -= 1;
}
}
桶排序
代码略