1 简单选择排序
存在一乱序数组A[0~n]
第一步:以A[0]作为参考点,比较A[1~n], 遍历完数组后,寻找到最小值A[k](或最大值)的下标k值,然后交换swap(A[0],A[k]);
第二步:以A[1]作为参考点,比较A[2~n], 遍历完数组后,寻找到最小值A[k](或最大值)的下标k值,然后交换swap(A[1],A[k]);
第i步:以A[i]作为参考点,比较A[(i+1)~n], 遍历完数组后,寻找到最小值A[k](或最大值)的下标k值,然后交换swap(A[i],A[k]);
时间复杂度:O(n^2)
空间复杂度:O(1)
#include<iostream>
using namespace std;
void selectSort(int arr[], int len)
{
if (len <= 0)return;
int temp = 0;
int index;
bool flag = false;
for (int i = 1; i < len; i++) {
flag = false;
temp = arr[i - 1];
for (int j = i; j < len; j++) {
if (temp > arr[j]) {
temp = arr[j];
index = j;
flag = true;
}
}
if (flag) {
arr[index] = arr[i - 1];
arr[i - 1] = temp;
}
}
}
void main()
{
int arr[] = { 12,3,54,2,6,17,14,43,13,23,32,43,25 ,19};
int len = sizeof(arr) / sizeof(arr[0]);
selectSort(arr, len);
for (int i = 0; i < len; ++i) {
cout << arr[i] << " ";
}
}
2 冒泡排序
要对元素个数为n(n>=1)的数组进行从小到大的顺序排序,从左到右两两对比,大的往后移,每轮完成之后,数组中最大的数都会沉到数组末尾。
时间复杂度:O(n^2)
空间复杂度:O(1)
#include<iostream>
using namespace std;
void bubbleSort(int arr[], int len)
{
if (len <= 0)return;
int temp = 0;
bool flag;
for (int i = 1; i < len; i++) {
flag = false;
for (int j = 0; j < len-i;j++) {
if (arr[j] > arr[j + 1]) {
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
flag = true;
}
}
if (flag == false)break;
}
}
void main()
{
int arr[] = { 12,3,54,2,6,38,17,14,32,43,25};
int len = sizeof(arr) / sizeof(arr[0]);
bubbleSort(arr, len);
for (int i = 0; i < len; ++i) {
cout << arr[i] << " ";
}
}
3 插入排序
要对元素个数为n(n>=1)的数组进行从小到大的顺序排序,首先可以把第一个元素arr[0]当做是一个初始有序序列,然后从这个有序序列之外的下一个元素开始,分别自后向前和前面已经有序的每一个元素进行比较,将该无序元素插入到第一次小于前面有序序列的某个元素的前一个位置。
时间复杂度:O(n^2)
空间复杂度:O(1)
#include<iostream>
using namespace std;
void insertionSort(int arr[], int len)
{
if (len <= 0)return;
int currentvalue;
int currentindex;
for (int i = 1; i < len; i++) {
currentvalue = arr[i];
currentindex = i;
while (currentindex>0&&arr[currentindex - 1] > currentvalue) {
arr[currentindex] = arr[currentindex - 1];
--currentindex;
}
arr[currentindex] = currentvalue;
}
}
void main()
{
int arr[] = { 12,3,54,2,6,17,14,32,43,25 ,19};
int len = sizeof(arr) / sizeof(arr[0]);
insertionSort(arr, len);
for (int i = 0; i < len; ++i) {
cout << arr[i] << " ";
}
}
4 快速排序
先从数列中取出一个数作为枢轴。
分区过程,将比这个数大的数全放到它的右边,小于或等于它的数全放到它的左边。
再对左右区间重复第二步,直到各区间只有一个数。
时间复杂度为O(nlogn)
空间复杂度为O(nlogn)
#include<iostream>
#include<vector>
using namespace std;
void quickSort(vector<int>& v,int left,int right) {
int l = left;
int r=right;
if (left < right) {
int base = v[left];
while (left < right) {
while (left < right&&v[right] >= base)right--;
v[left] = v[right];
while (left < right&&v[left] <= base)left++;
v[right] = v[left];
}
v[left] = base;
quickSort(v, l, left - 1);
quickSort(v, left + 1, r);
}
}
void main()
{
vector<int> v{ 1,45,2,34,45,23 ,11,15,9};
quickSort(v,0,8);
for (int i = 0; i < v.size(); i++) {
cout << v[i] << " ";
}
}
5 归并排序
归并排序是采用分治法的一个非常典型的应用,它要做两件事情: 第一: “分”, 就是将数组尽可能的分,一直分到原子级别。
第二: “并”,将原子级别的数两两合并排序,最后产生结果。 至于二个有序数列合并,只要比较二个数列的第一个数,谁小就先取谁安放到临时队列中, 取了后将对应数列中这个数删除,直到一个数列为空,再将另一个数列的数据依次取出即可。
时间复杂度为O(nlogn)
空间复杂度为O(n)
#include<iostream>
#include<vector>
using namespace std;
void Merge(int *arr, int n)
{
vector<int> temp(n, 0);
int b = 0;
int mid = n / 2;
int first = 0, second = mid;
while (first < mid && second < n)
{
if (arr[first] <= arr[second])
temp[b++] = arr[first++];
else
temp[b++] = arr[second++];
}
while (first < mid)
temp[b++] = arr[first++];
while (second < n)
temp[b++] = arr[second++];
for (int i = 0; i < n; ++i)
arr[i] = temp[i];
}
void MergeSort(int *arr, int n)
{
if (n <= 1)
return;
MergeSort(arr, n / 2);
MergeSort(arr + n / 2, n - n / 2);
Merge(arr, n);
}
void main()
{
int arr[9]={ 1,45,2,34,45,23 ,11,15,9 };
MergeSort(arr, 9);
for (int i = 0; i < 9; i++) {
cout << arr[i] << " ";
}
}
6 希尔排序
是改进的直接插入排序法,希尔排序是一种按照增量排序的方法。其中增量值是小于n的正整数。
shell排序的基本思想是:先取一个小于n的整数d1作为第一个增量,把文件的全部记录分成d1个组。所有距离为dl的倍数的记录放在同一个组中。先在各组内进行直接插入排序;然后,取第二个增量d2 < d1重复上述的分组和排序,直至所取的增量dt=1(dt < dt-l< …< d2< d1),即所有记录放在同一组中进行直接插入排序为止。
原理:又称增量缩小排序。先将序列按增量划分为元素个数相同的若干组,使用直接插入排序法进行排序,然后不断缩小增量直至为1,最后使用直接插入排序完成排序。
时间复杂度为O(nlogn)
空间复杂度为O(1)
#include<iostream>
using namespace std;
void shellSort(int * arrs,int arrLen)
{
int step = arrLen / 2;
while (step > 0) {
for (int i = step; i < arrLen; i++) {
int temp = arrs[i];
int j=i - step;
for (; j >= 0 && temp < arrs[j]; j = j - step)
arrs[j + step] = arrs[j];
arrs[j + step] = temp;
}
step /= 2;
}
}
void main()
{
int arr[9] = { 1,45,2,34,45,23 ,11,15,9 };
shellSort(arr, 9);
for (int i = 0; i < 9; i++) {
cout << arr[i] << " ";
}
}
7 堆排序
堆:①完全二叉树②所有父节点都大于子节点(大顶堆),所有父节点都小于子节点(小顶堆)
堆排序(Heap Sort),是利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构, 并同时满足堆积的性质:即子节点的键值或索引总是小于(或者大于)它的父节点。是改进的简单选择排序。 通常堆是通过一维数组来实现的,在起始数组为0的情形中,对于节点i:
其左子节点的下标为 (2*i+1); 其右子节点的下标为 (2*i+2); 其父节点的下标为 floor((i-1)/2)(向下取整)。
时间复杂度为O(nlogn)
空间复杂度为O(1)
#include<iostream>
#include<vector>
using namespace std;
void swap(vector<int>& tree, int i, int j) {
int temp = tree[i];
tree[i] = tree[j];
tree[j] = temp;
}
void heapify(vector<int>& tree, int index) {
int size = tree.size();
if (index >= size)return;
int c1 = 2 * index + 1;
int c2 = 2 * index + 2;
int max = index;
if (c1 < size&&tree[max] < tree[c1])max = c1;
if (c2 < size&&tree[max] < tree[c2])max = c2;
if (max != index) {
swap(tree, index, max);
heapify(tree, max);
}
}
void heapBuild(vector<int>& tree) {
int size = tree.size();
if (size <= 1)return;
int parent = (size - 2) / 2;
for (; parent >= 0; parent--) {
heapify(tree,parent);
}
}
void heapSort(vector<int>& tree){
int size = tree.size();
vector<int> temp;
heapBuild(tree);
int lastindex = size - 1;
for (; lastindex >= 0; lastindex--) {
temp.push_back(tree[0]);
swap(tree, 0, lastindex);
tree.pop_back();
heapify(tree,0);
}
for (int i = size-1; i >= 0; i--) {
tree.push_back(temp[i]);
}
}
void main()
{
vector<int> tree{ 1,5,9,35,23,19,45,24,6,9,0 };
heapSort(tree);
for (auto nums : tree) {
cout << nums << " ";
}
}
8 计数排序
计数排序的思想比较新颖,不再是基于元素之间的比较,而是将待排序序列的元素当作容器的索引,记录索引出现的次数;
比如临时容器的a[i] = n,表示元素i出现n次;
记录完出现次数之后,将临时容器从小到大将元素汇总起来,则变为有序;
计数排序的缺点:只能对整数序列进行排序,而且不适合最大元素和最小元素差得很大的情况(若相差很大则算法需要申请非常大的数据存储空间,并且该空间很大一部分在实际计算中并没有用到)
很多人都会说计数排序的时间复杂度为O(n),说性能优于快速排序,但其实并不是,因为差值d一般都不是一个小的数,是不容忽视的,这就是为什么说计数排序时间复杂度优于快速排序但快速排序的应用广的一部分原因。
另一部分原因是因为计数排序的缺陷,它只适用于整数序列,而且差值d太大的话空间复杂比较差,该缺陷有一个弥补的排序方法,那就是桶排序
时间复杂度为O(n+d)
空间复杂度为O(d)
#include<iostream>
#include<vector>
using namespace std;
void countSort(vector<int>& base) {
int size = base.size();
if (size <= 1)return;
int max = base[0];
int min = base[0];
for (int i = 0; i < size; i++) {
if (max < base[i])max = base[i];
if (min > base[i])min = base[i];
}
if (max == min)return;
int d = max - min;
vector<int> temp(d + 1, 0);
for (int i = size - 1; i >= 0; i--) {
temp[base[i] - min]++;
base.pop_back();
}
for (int i = 0; i < d + 1; i++) {
while (temp[i]) {
base.push_back(i+min);
temp[i]--;
}
}
}
void main()
{
vector<int> base{ 1,5,9,35,23,19,45,24,6,9,0 };
countSort(base);
for (auto nums : base) {
cout << nums << " ";
}
}
9 桶排序
桶排序是计数排序的一个优化,它解决了计数排序只适用于整数排序的限制,更是解决了不适用于最大值和最小值差值很大的情况.
桶排序的思想就是对最小值和最小值之间的元素进行瓜分,将他们分为多个区间,每个区间用一个桶(其实就是一个容器)来装.
前一个桶里的元素全都小于后一个桶里的元素,只需要利用别的常规排序算法将桶里面的元素进行排序使之有序即可使得原序列有序.
设桶个数为m,一般我们把时间复杂的记为O(n+m)。
空间复杂度:O(n+m)
#include<iostream>
#include<vector>
using namespace std;
void QuickSort(vector<int>& v, int l, int r) {
int size = v.size();
if (size <= 1)return;
if (l < r) {
int left = l;
int right = r;
int base = v[l];
while (l < r) {
while (l<r&&v[r]>=base)r--; v[l] = v[r];
while (l < r&&v[l] <= base)l++; v[r] = v[l];
}
v[l] = base;
QuickSort(v, left, l - 1);
QuickSort(v, l + 1, right);
}
}
void bucketSort(vector<int>& base) {
int size = base.size();
if (size <= 1)return;
int max = base[0];
int min = base[0];
for (int i = 1; i < size; i++) {
if (max < base[i])max = base[i];
if (min > base[i])min = base[i];
}
int d = max - min;
int bucketnum = base.size();
vector<vector<int>> bucket(bucketnum+1);
for (int i = size - 1; i >= 0; i--) {
int index = (base[i] - min) / (d / (bucketnum-1));
bucket[index].push_back(base[i]);
base.pop_back();
}
for (int i = 0; i <= bucketnum; i++) {
QuickSort(bucket[i], 0, bucket[i].size()-1);
}
for (int i = 0; i < bucketnum+1; i++) {
for (int nums : bucket[i])base.push_back(nums);
}
}
void main()
{
vector<int> base{ 1,5,9,35,23,19,45,24,6,9,0 ,63};
bucketSort(base);
for (auto nums : base) {
cout << nums << " ";
}
}
10 基数排序
使用了桶排序中桶的思想,但它比桶排序更精明,它只需要十个桶,因为他的排序思想是分别对元素中的个位,十位,百位....进行排序.
也就是说,首先对所有数以个位数的大小进行排序,然后再对所有数以他们的十位数进行排序,依次类推.
在整个过程中会使得原始序列逐渐趋近有序,待将最高位排完之后完全有序.
想想为什么是从个位开始而不是从最高位开始呢,按道理从最高位开始的话每次都能得出一部分数的正确大小关系.
确实可以从最高位开始,而且可以减少比较次数,但是从最高位开始会有一个致命缺点,那就是在如果从高位开始,在对高位相同的数继续排序时,又需要另外创建十个桶对他们排序,其实也就是说最终的结果就是真多每一个不同的元素都会为它创建一个桶, 如果待排序序列有10000个不同的元素,那么从高位开始比较的方法就需要创建10000个桶,而从个位开始比较的方法可以重复使用 那10个桶,如果序列个数更多那么这样的性能差异就更明显,所以虽然减少了比较次数但浪费了非常多的空间,得不偿失.
所以我们说基数排序的话都默认的是从个位开始比较的.
时间复杂度为O(d(n+r))
空间复杂度为O(n+r)
#include<iostream>
#include<cmath>
#include<vector>
using namespace std;
void radixSort(vector<int>& base) {
int size = base.size();
if (size <= 1)return;
int max = base[0];
for (int nums : base)max = max > nums ? max : nums;
int bit = 0;
while (max) {
bit++;
max /= 10;
}
vector<vector<int>> bucket(10);
for (int i = 0; i < bit; i++) {
for (int j = 0; j <size; j++) {
int radix = (base[j] / (int)(pow(10, i))) % 10;
bucket[radix].push_back(base[j]);
}
int m = 0;
for (int k = 0; k < 10; k++) {
for (int nums : bucket[k])base[m++] = nums;
bucket[k].clear();
}
}
}
void main()
{
vector<int> base{ 1,5,9,35,23,19,45,24,6,9,0 ,63 };
radixSort(base);
for (auto nums : base) {
cout << nums << " ";
}
}