本文整理自:
https://www.runoob.com/w3cnote/ten-sorting-algorithm.html
https://www.cnblogs.com/chengxiao/p/6104371.html
https://www.biancheng.net/algorithm/what-is-algorithm.html
常见的内部排序算法有:插入排序、希尔排序、选择排序、冒泡排序、归并排序、快速排序、堆排序、基数排序等。用一张图概括:
名词解释:
n:数据规模
k:"桶"的个数
In-place:占用常数内存,不占用额外内存
Out-place:占用额外内存
稳定性:排序后 2 个相等键值的顺序和排序之前它们的顺序相同
1)比较相邻的元素。如果第一个比第二个大,就交换他们两个位置;
2)对每一对相邻元素做同样的处理,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是本轮最大的数;
3)除上轮最大值外,剩余数重复上述两个步骤,每轮比较结束后得到本轮最大值;
4)直到没有任何一对数字需要比较,得到从小到大的排序结果。
C++ 语言
#include
using namespace std;
template<typename T> //整数或浮点数皆可使用,若要使用类(class)或结构体(struct)时必须重载大于(>)运算符
void bubble_sort(T 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]);
}
int main() {
int arr[] = { 61, 17, 29, 22, 34, 60, 72, 21, 50, 1, 62 };
int len = (int) sizeof(arr) / sizeof(*arr);
bubble_sort(arr, len);
for (int i = 0; i < len; i++)
cout << arr[i] << ' ';
cout << endl;
float arrf[] = { 17.5, 19.1, 0.6, 1.9, 10.5, 12.4, 3.8, 19.7, 1.5, 25.4, 28.6, 4.4, 23.8, 5.4 };
len = (float) sizeof(arrf) / sizeof(*arrf);
bubble_sort(arrf, len);
for (int i = 0; i < len; i++)
cout << arrf[i] << ' '<<endl;
return 0;
}
1)首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置;
2)再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾;
2)重复第二步,直到所有元素均排序完毕。
C++代码(只有函数处理部分)
template<typename T> //整数或浮点数皆可使用,若要使用类(class)或结构体(struct)时必须重载大于(>)运算符
void selection_sort(std::vector<T>& arr) {
for (int i = 0; i < arr.size() - 1; i++) {
int min = i;
for (int j = i + 1; j < arr.size(); j++)
if (arr[j] < arr[min])
min = j;
std::swap(arr[i], arr[min]);
}
}
1)第一将待排序序列第一个元素看做一个有序序列,把第二个元素到最后一个元素当成是未排序序列;
2)从头到尾依次扫描未排序序列,将扫描到的每个元素插入有序序列的适当位置。(如果待插入的元素与有序序列中的某个元素相等,则将待插入元素插入到相等元素的后面。)
C++代码(只有函数处理部分)
void insertion_sort(int arr[],int len){
for(int i=1;i<len;i++){
int key=arr[i];
int j=i-1;
while((j>=0) && (key<arr[j])){
arr[j+1]=arr[j];
j--;
}
arr[j+1]=key;
}
}
基本思想
先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录"基本有序"时,再对全体记录进行依次直接插入排序。
1)把记录按下标的一定增量分组;
2)对每组使用直接插入排序算法排序;
3)随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止。
希尔排序的基本步骤,在此我们选择增量gap=length/2,缩小增量继续以gap = gap/2的方式,这种增量选择我们可以用一个序列来表示,{n/2,(n/2)/2…1},称为增量序列。
在希尔排序的理解时,倾向于对于每一个分组,逐组进行处理,但在代码实现中,我们可以不用这么按部就班地处理完一组再调转回来处理下一组。
C++代码(只有函数处理部分)
template<typename T>
void shell_sort(T array[], int length) {
//int h = 1;
//while (h < length / 2) {
// h = 2 * h + 1;
//}
int h = length / 2;
while (h >= 1) {
for (int i = h; i < length; i++) {
for (int j = i; j >= h && array[j] < array[j - h]; j -= h) {
std::swap(array[j], array[j - h]);
}
}
h = h / 2;
}
}
归并排序是一种基于分治思想的排序算法,时间复杂度为O(nlogn),效率仅次于快速排序算法。
下面我们以对序列 {7,5,2,4,1,6,3,0} 进行升序排序为例,给您讲解归并排序算法的运行流程。
整个归并排序算法的实现过程分为以下 2 步:
归并的核心思想是:不断地进行两两合并,合并过程中完成对新序列的排序操作。整个归并如下图所示。
C语言
#include
//实现分割操作的函数
void merge_sort(int* arr, int p, int q);
//实现归并操作的函数
void merge(int* arr, int p, int mid, int q);
int main() {
int i = 0;
int arr[8] = { 7,5,2,4,1,6,3,0 };
//对 arr 数组中第 1 至 8 个元素进行归并排序
merge_sort(arr, 1, 8);
while (i < 8)
{
printf("%d ", arr[i]);
i++;
}
return 0;
}
//实现分割操作的函数,[p,q] 用于指定归并排序的区域范围,
void merge_sort(int* arr, int p, int q) {
int mid;
if (arr == NULL || p > q || p == q) {
return ;
}
mid = (p + q) / 2;
//将 [p,q] 分为[p,mid] 和 [mid+1,q] 区域
merge_sort(arr, p, mid);
merge_sort(arr, mid + 1, q);
//对分好的 [p,mid] 和 [mid,q] 进行归并操作
merge(arr, p, mid, q);
}
//实现归并操作的函数,归并的 2 个区域分别为 [p,mid] 和 [mid+1,q]
void merge(int* arr, int p, int mid, int q) {
int i,j,k;
int leftarr[100], rightarr[100];
int numL = mid - p + 1;
int numR = q - mid;
//将 arr 数组中 [p,mid] 区域内的元素逐一拷贝到 leftarr 数组中
for (i = 0; i < numL; i++) {
leftarr[i] = arr[p - 1 + i];
}
//将 leftarr 数组中最后一个元素设置为足够大的数。
leftarr[i] = 2147483647;
//将 arr 数组中 [mid+1,q] 区域内的元素逐一拷贝到 rightarr 数组中
for (i = 0; i < numR; i++) {
rightarr[i] = arr[mid + i];
}
//将 rightarr 数组中最后一个元素设置为足够大的数。
rightarr[i] = 2147483647;
i = 0;
j = 0;
//逐一比较 leftarr 和 rightarr 中的元素,每次将较小的元素拷贝到 arr 数组中的 [p,q] 区域内
for (k = p; k <= q; k++) {
if (leftarr[i] <= rightarr[j]) {
arr[k - 1] = leftarr[i];
i++;
}
else {
arr[k - 1] = rightarr[j];
j++;
}
}
}
1)从数列中挑出一个元素,称为 “基准”(pivot);
2)重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
3)递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序;
C语言
#include
// arr 为待排序数组,[p,q] 用于指定排序区域
int partition(int *arr,int p, int q) {
int temp = 0;
// lo、hi分别表示指向首个元素和倒数第 2 个元素的指针
int lo = p;
int hi = q - 1;
//pivot 表示选中的中间值
int pivot = arr[q];
while (1)
{
//lo从左往右遍历,直至找到一个不小于 pivot 的元素
while (arr[lo] < pivot) {
lo++;
};
//hi从右往左遍历,直至找到一个不大于 pivot 的元素
while (hi > 0 && arr[hi] > pivot) {
hi--;
}
//如果 lo≥hi,退出循环
if (lo >= hi)
{
break;
}
else {
//交换 arr[lo] 和 arr[hi] 的值
temp = arr[lo];
arr[lo] = arr[hi];
arr[hi] = temp;
// lo 和 hi 都向前移动一个位置,准备继续遍历
lo++;
hi--;
}
}
//交换 arr[lo] 和 arr[q] 的值
temp = arr[lo];
arr[lo] = pivot;
arr[q] = temp;
//返回中间值所在序列中的位置
return lo;
}
void quick_sort(int* arr, int p, int q) {
int par;
//如果待排序序列不存在,或者仅包含 1 个元素,则不再进行分割
if (q - p <= 0) {
return;
}
else {
//调用 partition() 函数,分割 [p,q] 区域
par = partition(arr, p, q);
//以 [p,par-1]作为新的待排序序列,继续分割
quick_sort(arr, p, par - 1);
//以[par+1,q]作为新的待排序序列,继续分割
quick_sort(arr, par + 1, q);
}
}
int main()
{
int i = 0;
int arr[10] = { 35,33,42,10,14,19,27,44,26,31 };
//对于 arr 数组中所有元素进行快速排序
quick_sort(arr, 0, 9);
for (; i < 10; i++) {
printf("%d ", arr[i]);
}
return 0;
}
堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。堆排序可以说是一种利用堆的概念来排序的选择排序。分为两种方法:
大顶堆:每个节点的值都大于或等于其子节点的值,在堆排序算法中用于升序排列;
小顶堆:每个节点的值都小于或等于其子节点的值,在堆排序算法中用于降序排列;
堆排序的平均时间复杂度为 Ο(nlogn)。
算法步骤
1)创建一个堆 H[0……n-1];
2)把堆首(最大值)和堆尾互换;
3)把堆的尺寸缩小 1,并调用 shift_down(0),目的是把新的数组顶端数据调整到相应位置;
4)重复步骤 2),直到堆的尺寸为 1。
C++
#include
#include
using namespace std;
void max_heapify(int arr[], int start, int end) {
// 建立父節點指標和子節點指標
int dad = start;
int son = dad * 2 + 1;
while (son <= end) { // 若子節點指標在範圍內才做比較
if (son + 1 <= end && arr[son] < arr[son + 1]) // 先比較兩個子節點大小,選擇最大的
son++;
if (arr[dad] > arr[son]) // 如果父節點大於子節點代表調整完畢,直接跳出函數
return;
else { // 否則交換父子內容再繼續子節點和孫節點比較
swap(arr[dad], arr[son]);
dad = son;
son = dad * 2 + 1;
}
}
}
void heap_sort(int arr[], int len) {
// 初始化,i從最後一個父節點開始調整
for (int i = len / 2 - 1; i >= 0; i--)
max_heapify(arr, i, len - 1);
// 先將第一個元素和已经排好的元素前一位做交換,再從新調整(刚调整的元素之前的元素),直到排序完畢
for (int i = len - 1; i > 0; i--) {
swap(arr[0], arr[i]);
max_heapify(arr, 0, i - 1);
}
}
int main() {
int arr[] = { 3, 5, 3, 0, 8, 6, 1, 5, 8, 6, 2, 4, 9, 4, 7, 0, 1, 8, 9, 7, 3, 1, 2, 5, 9, 7, 4, 0, 2, 6 };
int len = (int) sizeof(arr) / sizeof(*arr);
heap_sort(arr, len);
for (int i = 0; i < len; i++)
cout << arr[i] << ' ';
cout << endl;
return 0;
}
1)找出待排序的数组中最大和最小的元素;
2)统计数组中每个值为i的元素出现的次数,存入数组C的第i项;
3)对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加);
4)反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1。
C语言
#include
#define N 7 //待排序序列中的元素个数
#define MAX 100 //待排序序列中的最大值不能超过 100
//找到数组中的最大值
int getMax(int array[]) {
int max = array[0];
for (int i = 1; i < N; i++) {
if (array[i] > max)
max = array[i];
}
return max;
}
void countingSort(int array[]) {
//第 1 步,找到序列中的最大值
int max = getMax(array);
int count[MAX] = {0};
int output[N] = {0};
//第 2 步,统计各个元素的出现次数,并存储在相应的位置上
for (int i = 0; i < N; i++) {
count[array[i]]++;
}
//第 3 步,累加 count 数组中的出现次数
for (int i = 1; i <= max; i++) {
count[i] += count[i - 1];
}
//第 4 步,根据 count 数组中的信息,找到各个元素排序后所在位置,存储在 output 数组中
for (int i = N - 1; i >= 0; i--) {
output[count[array[i]] - 1] = array[i];
count[array[i]]--;
}
// 将 output 数组中的数据原封不动地拷贝到 array 数组中
for (int i = 0; i < N; i++) {
array[i] = output[i];
}
}
void printArray(int array[]) {
for (int i = 0; i < N; ++i) {
printf("%d ", array[i]);
}
}
int main() {
int array[] = { 4, 2, 2, 8, 3, 3, 1 };
//进行计数排序
countingSort(array);
printArray(array);
}
桶排序算法的核心思想是:将待排序序列中的元素根据规则分组,每一组采用快排、插入排序等算法完成排序,然后再将所有组进行合并,最终就可以得到一个有序序列。
也就是说,采用桶排序算法对目标序列进行排序,会经历下图所示的三个阶段:
C语言
#include
#include
#define N 7 // 待排序序列中的元素个数
#define NBUCKET 6 // 桶的数量
#define INTERVAL 10 // 每个桶能存放的元素个数
//建立桶
struct Node {
float data;
struct Node *next;
};
void BucketSort(float arr[]);
struct Node *InsertionSort(struct Node *list);
void print(float arr[]);
int main() {
float array[N] = { 0.42, 0.32, 0.23, 0.52, 0.25, 0.47, 0.51 };
BucketSort(array);
print(array);
return 0;
}
// 桶排序,arr 为待排序序列
void BucketSort(float arr[]) {
int i, j;
struct Node **buckets;
// 创建所有桶
buckets = (struct Node **)malloc(sizeof(struct Node *) * NBUCKET);
// 设置每个桶为空桶
for (i = 0; i < NBUCKET; ++i) {
buckets[i] = NULL;
}
// 根据规定,将 arr 中的每个元素分散存储到各个桶中
for (i = 0; i < N; ++i) {
struct Node *current;
int pos = arr[i] * 10; //根据规则,确定元素所在的桶
//创建存储该元素的存储块,并连接到指定的桶中
current = (struct Node *)malloc(sizeof(struct Node));
current->data = arr[i];
current->next = buckets[pos];
buckets[pos] = current;
}
// 调用自定义的排序算法,对各个桶进行排序
for (i = 0; i < NBUCKET; ++i) {
buckets[i] = InsertionSort(buckets[i]);
}
// 合并所有桶内的元素
for (j = 0, i = 0; i < NBUCKET; ++i) {
struct Node *node;
node = buckets[i];
while (node) {
arr[j++] = node->data;
node = node->next;
}
}
}
// 自定义的排序算法,用于对各个桶内元素进行排序
struct Node *InsertionSort(struct Node *list) {
struct Node *k, *nodeList;
if (list == NULL || list->next == NULL) {
return list;
}
nodeList = list;
k = list->next;
nodeList->next = NULL;
while (k != NULL) {
struct Node *ptr;
if (nodeList->data > k->data) {
struct Node *tmp;
tmp = k;
k = k->next;
tmp->next = nodeList;
nodeList = tmp;
continue;
}
for (ptr = nodeList; ptr->next != 0; ptr = ptr->next) {
if (ptr->next->data > k->data)
break;
}
if (ptr->next != 0) {
struct Node *tmp;
tmp = k;
k = k->next;
tmp->next = ptr->next;
ptr->next = tmp;
continue;
}
else {
ptr->next = k;
k = k->next;
ptr->next->next = 0;
continue;
}
}
return nodeList;
}
void print(float ar[]) {
int i;
for (i = 0; i < N; ++i) {
printf("%.2f ", ar[i]);
}
}
基数排序算法的核心思想是:从低位到高位,依次提取出待排序列各个元素中相同位置上的数字(或字符),通过比较它们的大小调整各个元素的位置,最终实现升序或者降序排序。
以对 {121, 432, 564, 23, 1, 45, 788} 做升序排序为例,下图演示了基数排序算法的实现过程:
C语言
#include
#define N 7
#define MAX 100 //限制各个元素各数位上的值不能超过 100
//计数排序算法,place 表示以指定数位为准,对序列中的元素进行排序
void countingSort(int array[], int place) {
int output[N];
//假设第一个元素指定数位上的值最大
int max = (array[0] / place) % 10;
//找到真正数位上值最大的元素
for (int i = 1; i < N; i++) {
if (((array[i] / place) % 10) > max)
max = array[i];
}
//初始化一个 count 数组
int count[MAX] = {0};
//统计各个元素出现的次数
for (int i = 0; i < N; i++)
count[(array[i] / place) % 10]++;
//累加 count 数组中的出现次数
for (int i = 1; i < 10; i++)
count[i] += count[i - 1];
//根据 count 数组中的信息,找到各个元素排序后所在位置,存储在 output 数组中
for (int i = N - 1; i >= 0; i--) {
output[count[(array[i] / place) % 10] - 1] = array[i];
count[(array[i] / place) % 10]--;
}
//将 output 数组中的数据原封不动地拷贝到 array 数组中
for (int i = 0; i < N; i++)
array[i] = output[i];
}
//找到整个序列中的最大值
int getMax(int array[]) {
int max = array[0];
for (int i = 1; i < N; i++)
if (array[i] > max)
max = array[i];
return max;
}
//基数排序算法
void radixSort(int array[]) {
//找到序列中的最大值
int max = getMax(array);
//根据最大值具有的位数,从低位依次调用计数排序算法
for (int place = 1; max / place > 0; place *= 10)
countingSort(array, place);
}
//输出 array 数组中的数据
void printArray(int array[]) {
for (int i = 0; i < N; ++i) {
printf("%d ", array[i]);
}
}
int main() {
int array[N] = { 121, 432, 564, 23, 1, 45, 788 };
radixSort(array);
printArray(array);
}