解读排序算法(C++版本)! |
一. 排序的基本概念
1.1. 排序的定义
►所谓排序,就是整理表中的记录,使之按关键字递增或者递减有序排列(说明:排序可以存在相同关键字的记录)。
1.2. 内排序和外排序
1.3. 稳定性及复杂度
►常见的快速排序、归并排序、堆排序、冒泡排序等属于比较排序 。在排序的最终结果里,元素之间的次序依赖于它们之间的比较。每个数都必须和其他数进行比较,才能确定自己的位置。
►计数排序、基数排序、桶排序则属于非比较排序。非比较排序是通过确定每个元素之前,应该有多少个元素来排序。针对数组 a r r arr arr,计算 a r r [ i ] arr[i] arr[i] 之前有多少个元素,则唯一确定了 a r r [ i ] arr[i] arr[i] 在排序后数组中的位置。非比较排序只要确定每个元素之前的已有的元素个数即可,所有一次遍历即可解决。算法时间复杂度 O ( n ) O(n) O(n)。
二. 插入排序
►基本思路如下:
2.1. 直接插入排序
2.1.1. 算法实现
#include <iostream>
#include <vector>
using namespace std;
class Solution
{
public:
bool insertSort1(vector<int> &arr) //1、对R[0,...,n-1]按递增有序进行直接插入排序
{
if (arr.size() == 0)
return false;
int i, j, tmp;
for (i = 1; i < arr.size(); i++)
{
tmp = arr[i];
j = i - 1; //从右往左再有序区R[0,...,i-1]中找R[i]的插入位置;
while (j >= 0 && tmp < arr[j])
{
arr[j + 1] = arr[j]; //将大于R[i]的关键字后移;
j--;
}
arr[j + 1] = tmp; //在R[j+1]处插入R[i];
}
return true;
}
bool insertSort2(vector<int> &arr) //2、全部利用for循环
{
if (arr.size() == 0)
return false;
int tmp;
for (int i = 1; i < arr.size(); i++)
for (int j = i; j > 0 && arr[j] < arr[j - 1]; j--)
{ //交换
tmp = arr[j];
arr[j] = arr[j - 1];
arr[j - 1] = tmp;
}
return true;
}
};
int main()
{
Solution sol;
vector<int> arr{5, 3, 6, 8, 1, 7, 9, 4, 2};
if (sol.insertSort1(arr))
for (int i = 0; i < arr.size(); i++)
cout << arr[i] << " ";
cout << endl;
system("pause");
return 0;
}
1 2 3 4 5 6 7 8 9
请按任意键继续. . .
2.1.2. 算法分析
2.2. 希尔排序
2.2.1. 算法实现
#include <iostream>
#include <vector>
using namespace std;
class Solution
{
public:
bool insertSort1(vector<int> &arr)
{
if (arr.size() == 0)
return false;
int tmp, j, len = arr.size();
int d = len / 2; //增量设置初始值;d = len >> 1; //效率更高,右移;
while (d > 0)
{
for (int i = d; i < len; i++) //对相隔d位置的元素组直接插入排序
{
tmp = arr[i];
j = i - d;
while (j >= 0 && tmp < arr[j])
{
arr[j + d] = arr[j];
j -= d;
}
arr[j + d] = tmp;
}
d /= 2; //减小增量;
}
return true;
}
};
int main()
{
Solution sol;
vector<int> arr{5, 3, 6, 8, 1, 7, 9, 4, 2};
if (sol.insertSort1(arr))
for (int i = 0; i < arr.size(); i++)
cout << arr[i] << " ";
cout << endl;
system("pause");
return 0;
}
1 2 3 4 5 6 7 8 9
请按任意键继续. . .
2.2.2. 算法分析
三. 选择排序
3.1. 直接选择排序
3.1.1. 算法实现
#include <iostream>
#include <vector>
using namespace std;
class Solution
{
public:
bool selectSort(vector<int> &arr)
{
if (arr.size() == 0)
return false;
int i, j, tmp;
for (i = 0; i < arr.size() - 1; i++)
{
int min_index = i; //每一趟选择中,先假设起始位置元素值最小;
for (j = i + 1; j < arr.size(); j++)
{
/* if (arr[min_index] > arr[j])
min_index = j;*/
min_index = arr[min_index] > arr[j] ? j : min_index; //如果当前位置元素小于minPos位置元素,要更新;
}
if (min_index != i) //交换位置
{
tmp = arr[min_index];
arr[min_index] = arr[i];
arr[i] = tmp;
}
}
return true;
}
};
int main()
{
Solution sol;
vector<int> arr{5, 3, 6, 8, 1, 7, 9, 4, 2};
if (sol.selectSort(arr))
for (int i = 0; i < arr.size(); i++)
cout << arr[i] << " ";
cout << endl;
system("pause");
return 0;
}
1 2 3 4 5 6 7 8 9
请按任意键继续. . .
3.1.2. 算法分析
3.2. 堆排序
►堆的定义:
►完全二叉树角度理解:
3.2.1. 算法实现
►堆排序算法设计: 堆排序的关键是构造堆,这里采用筛选算法建堆(这里按照大根堆来)。
►注意:这里只有根节点不满足大根堆,左右子树都满足了大根堆。
►筛选算法:
筛选建立大根堆的过程(左右子树都是大根堆,只有根节点不是)
►筛选算法代码实现如下:采用了2种方式迭代策略和递归策略!
- 方式1:迭代策略
/*===========1、筛选或者调整堆的算法 ===========*/
void shif(vector<int> &arr, int index, int size)
{
int i = index, j = 2 * i + 1; //R[j]是R[i]的左孩子;
int tmp = arr[i]; //临时变量存放根节点;
while (j < size)
{
if (j < size - 1 && arr[j] < arr[j + 1])
j++; //j指向大孩子;
if (tmp < arr[j]) //双亲小
{
arr[i] = arr[j]; //将R[j]的值调整到双亲节点上;
i = j;
j = 2 * i + 1;
}
else
break; //双亲大,不需要调整;
}
arr[i] = tmp; //放原来根节点的记录;
}
- 方式2:递归策略
void shif(vector<int> &arr, int index, int size)
{
int left = 2 * index + 1;
int right = left + 1;
int maxIndex = index;
if (left < size && arr[left] > arr[maxIndex])
maxIndex = left;
if (right < size && arr[right] > arr[maxIndex])
maxIndex = right;
if (maxIndex != index)
{
swap(arr[maxIndex], arr[index]);
shif(arr, maxIndex, size);
}
}
►注意:下面算法实现的过程中考虑到vector向量的下标是从0开始的,与上面的分析稍微有点差别!
#include <iostream>
#include <vector>
using namespace std;
class Solution
{
public:
/*===========1、堆排序算法 =====================*/
void heapSort(vector<int> &arr)
{
int len = arr.size();
buildHeap(arr, len);
for (int i = len - 1; i >= 1; i--)
{
swap(arr[0], arr[i]); // 将当前最大的放置到数组末尾;
shift(arr, 0, i); //筛选arr[0]节点到arr[i]间节点,构造大根堆;
}
}
/*===========2、循环建立大根堆 ==================*/
void buildHeap(vector<int> &arr, int len)
{
for (int i = len / 2 + 1; i >= 0; i--) //从最后一个非叶子节点向上,直到第一节点;
shift(arr, i, len);
}
/*===========3、筛选或者调整堆的算法(递归策略) ============*/
void shift(vector<int> &arr, int i, int len)
{
int left = 2 * i + 1; //左孩子索引;
int right = 2 * i + 2; //右孩子索引;
int maxIndex = i; //假定父节点为最大索引;
if (left < len && arr[left] > arr[maxIndex]) //左孩子存在,并且大于父节点;
maxIndex = left;
if (right < len && arr[right] > arr[maxIndex]) //右孩子存在并且大于根节点;
maxIndex = right;
if (maxIndex != i)
{
swap(arr[maxIndex], arr[i]);
shift(arr, maxIndex, len); //左子树或者右子树递归调用;
}
}
// /*===========3、筛选或者调整堆的算法(迭代策略) ============*/
// void shift(vector<int> &arr, int index, int len)
// {
// int i = index, j = 2 * i + 1; //arr[j]arr[i]的左孩子;
// int tmp = arr[i]; //临时变量存放根节点;
// while (j < len)
// {
// if (j < len - 1 && arr[j] < arr[j + 1]) //纯在右孩子且右孩子值大;
// j++; //j指向右孩子;
// if (tmp < arr[j]) //双亲小
// {
// arr[i] = arr[j]; //将R[j]的值调整到双亲节点上;
// i = j;
// j = 2 * i + 1;
// }
// else
// break; //双亲大,不需要调整;
// }
// arr[i] = tmp; //放原来根节点的记录;
// }
/*===========4、交换元素 =======================*/
void swap(int &a, int &b)
{
int tmp = a;
a = b;
b = tmp;
}
};
int main()
{
Solution sol;
vector<int> arr{4, 3, 5, 2, 1, 6};
sol.heapSort(arr); //注意vector的下标是从0开始的;
for (int i = 0; i < arr.size(); i++)
cout << arr[i] << " ";
cout << endl;
system("pause");
return 0;
}
1 2 3 4 5 6
请按任意键继续. . .
►例子: 设待排序的表有10个记录,其关键字分别为 { 6 , 8 , 7 , 9 , 0 , 1 , 3 , 2 , 4 , 5 } \{6,8,7,9,0,1,3,2,4,5\} {6,8,7,9,0,1,3,2,4,5}。说明采用堆排序方法进行的过程。
3.2.2. 算法分析
四. 交换排序
4.1. 冒泡排序
►例如下图中: 7 7 7 个数字需要 6 6 6 趟,第 1 1 1 趟需要比较 6 6 6,第 2 2 2 次需要比较 5 5 5,一直到第 6 6 6 趟比较 1 1 1 次,也就是第 i i i 趟需要比较 7 − i 7-i 7−i 次;
4.1.1. 算法实现
#include <iostream>
#include <vector>
using namespace std;
class Solution
{
public:
bool bubbleSort(vector<int> &arr)
{
if (arr.size() == 0)
return false;
int i, j, tmp, len = arr.size();
for (i = 0; i < len - 1; i++) //趟数,n个数,只需要n-1趟就够了;
for (j = 0; j < len - 1 - i; j++) //比较次数;
{
if (arr[j] > arr[j + 1])
{
tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
}
}
return true;
}
};
int main()
{
Solution sol;
vector<int> arr{5, 3, 6, 8, 1, 7, 9, 4, 2};
if (sol.bubbleSort(arr))
for (int i = 0; i < arr.size(); i++)
cout << arr[i] << " ";
cout << endl;
system("pause");
return 0;
}
1 2 3 4 5 6 7 8 9
请按任意键继续. . .
4.1.2. 算法改进
#include <iostream>
#include <vector>
using namespace std;
class Solution
{
public:
bool bubbleSort(vector<int> &arr)
{
if (arr.size() == 0)
return false;
bool flag = true;
int i, j, tmp, len = arr.size();
for (i = 0; i < len - 1; i++) //趟数,n个数,只需要n-1趟就够了;
{
for (j = 0; j < len - 1 - i; j++) //比较次数;
{
if (arr[j] > arr[j + 1])
{
tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
flag = false;
}
}
if (flag)
break; //中途结束循环
}
return true;
}
};
int main()
{
Solution sol;
vector<int> arr{5, 3, 6, 8, 1, 7, 9, 4, 2};
if (sol.bubbleSort(arr))
for (int i = 0; i < arr.size(); i++)
cout << arr[i] << " ";
cout << endl;
system("pause");
return 0;
}
4.1.3. 算法分析
4.2. 快速排序
►快速排序是由冒泡排序改进而来的,基本思想: 在待排序的那个元素中任取一个元素(通常是第一个元素)作为基准,把该元素放入适当的位置后,数据序列被此元素划分为两部分,所有关键字比该元素关键字小的元素放置在前一部分,所有关键字比它大的元素放置在后一部分,并把元素排在这2部分的中间(称为元素归为),这个过程是一趟快速排序,之后对所有划分出来的两部分分别重复上述过程,直至每部分内只有一个元素或为空为止,简而言之,每趟将表的第一个元素放入适当位置,将表一分为二,对子表按照递推方式继续这种划分,直至划分的子表长为1或者0。
快速排序过程!
4.2.1. 算法实现
#include <iostream>
#include <vector>
using namespace std;
class Solution
{
public:
void quickSort(vector<int> &arr, int low, int high) //low开始,high结束;
{
int i = low, j = high, tmp;
if (low < high) //归体,看递归树,每次递归划分的视乎区间至少存在2个元素的情况;或者使用low!=high
{
tmp = arr[low]; //用区间的第一个元素作为基准;
while (i != j) //看做2个指针从两端交替向中间扫描,直至i=j为止;
{
while (j > i && arr[j] > tmp)
j--;
arr[i] = arr[j];
while (i < j && arr[i] < tmp)
i++;
arr[j] = arr[i];
}
arr[i] = tmp;
quickSort(arr, low, i - 1); //对左区间递归排序;
quickSort(arr, i + 1, high); //对右区间递归排序;
}
//递归出口,不需要任何操作;
}
};
int main()
{
Solution sol;
vector<int> arr{5, 3, 6, 8, 1, 7, 9, 4, 2};
sol.quickSort(arr, 0, arr.size() - 1);
for (int i = 0; i < arr.size(); i++)
cout << arr[i] << " ";
cout << endl;
system("pause");
return 0;
}
1 2 3 4 5 6 7 8 9
请按任意键继续. . .
4.2.2. 算法分析
五. 归并排序
5.1. 算法实现
- merge():一次二路归并,将2个相邻的有序子序列归并为一个有序序列。空间复杂度为O(high-low+1)(开辟了一个新的空间,用来存放排好序的元素);
- mergeSort():递归进行;
#include <iostream>
#include <vector>
using namespace std;
class Solution
{
public:
/*=========1、二路归并,将2个相邻的有序子序列归并为一个有序序列。=========*/
void merge(vector<int> &arr, int low, int mid, int high)
{
int i = low, j = mid + 1, k = 0; //k是R1的下标,i、j分别是第1、2段的下标。
vector<int> R1(high - low + 1); //空间复杂度O(high-low+1)
while (i <= mid && j <= high)
{
if (arr[i] <= arr[j]) //将第1段中的记录放到R1中;
{
R1[k] = arr[i];
i++;
k++;
}
else //将第2段中的记录放到R1中;
{
R1[k] = arr[j];
j++;
k++;
}
}
while (i <= mid) //将第1段中余下部分复制到R1中;
{
R1[k] = arr[i];
i++;
k++;
}
while (j <= high) //将第2段中余下部分复制到R1中;
{
R1[k] = arr[j];
j++;
k++;
}
//将R1复制回R中;
for (k = 0, i = low; i <= high; k++, i++)
arr[i] = R1[k];
R1.shrink_to_fit(); // R1最小化它的容量;
}
/*=========2、递归排序 =========*/
void mergeSort(vector<int> &arr, int low, int high)
{
if (low < high) //或者low!=high都可以,只要保证,拆分到单个节点就可以,此时low=high;
{
/*不用(upper+low)/2,避免upper+low溢出(因为low+upper都是整形数据,是有界限的)*/
int mid = low + (high - low) / 2;
mergeSort(arr, low, mid);
mergeSort(arr, mid + 1, high);
merge(arr, low, mid, high);
}
}
};
int main()
{
Solution sol;
vector<int> arr{5, 3, 6, 8, 1, 7, 9, 4, 2};
sol.mergeSort(arr, 0, arr.size() - 1);
for (int i = 0; i < arr.size(); i++)
cout << arr[i] << " ";
cout << endl;
system("pause");
return 0;
}
1 2 3 4 5 6 7 8 9
请按任意键继续. . .
5.2. 算法分析
六. 基数排序
6.1. 算法实现
►