注:
算法稳定性
定义:待排序序列中的相同关键字记录经过排序后相对次序保持不变。比如Xm=xn,原本Xm在Xn之前,排序后依然Xm在Xn之前。
条件:算法的稳定性由具体算法决定,且在一定条件下算法稳定性也会发生转变。
稳定性意义:只有在排序一个复杂对象的多个数字属性且初始顺序存在意义时,才需要注意算法的稳定性,即需要尽量保持原有排序序列的意义。比如原本商品按价格高低排序,想着想要按销量排序,且使得同销量的商品依然保持价格高低的排序,这是可以使用稳定性算法。
重复进行相邻数组元素的两两比较,并按规则进行交换,直到没有元素再需要交换。最终使得大的元素逐渐沉到数列底部,相较小的元素浮现到数列前端。
1 比较相邻两个元素,如果第一个比第二个大,就交换位置。
2 从第一对开始,对数组中的每一对相邻的两个元素重复步骤1,使得最大的元素沉到数组底部。
3 重复步骤2,除了底部已经排序好的元素。(每一趟都会多一个以排序好的元素)
4 重复以上步骤直到排序完成。
最好:T(n)=O(n);数据全部正序
最差:T(n)=O(n^2);数据全部反序
平均:T(n)=O(n^2);
#include
using namespace std;
void swap(int &a, int &b) {//交换函数
int tmp = a;
a = b;
b = tmp;
}
void bubbleSort(int a[],int len) {
for (int i = 0; i < len; ++i) {//排列len趟完成排序
for (int j = 0; j < len - i - 1; ++j) {//每趟排序的元素都从第一个元素开始到尾部没有排序好的元素。第一趟为第一个元素到最后一个元素,排出了最大元素。第二趟为第一个元素到倒数第二个元素(排除以排序好的元素),排出第二大的元素。
if (a[j] > a[j + 1]) {
swap(a[j], a[j + 1]);
}
}
}
}
int main() {
int a[] = { 520,0,1,9,56,100,1,85,5,3,6 };
int len = sizeof(a) / sizeof(a[0]);
bubbleSort(a, len);
for (int i = 0; i < len; ++i) {
cout << " " << a[i];
}
getchar();
return 0;
}
//运行结果:
0 1 1 3 5 6 9 56 85 100 520
1**增加了反向冒泡。传统冒泡排序每一趟排序一次,找出一个最大值。改进后每一趟排序两次,正向冒泡,找出最大的元素;反向冒泡,找出最小元素,使得一次可以得到两个最终值。从而减少排序趟数。**
2**增加了标志点flag**。目的在于记录每一趟最后一次交换的元素位置,即表示标志点之后或者之前的位置已经排好,后续无需再排,缩小排序区间,减少排序次数。
#include
using namespace std;
void swap(int &a, int &b) {
int tmp = a;
a = b;
b = tmp;
}
void bubbleSort(int a[],int len) {
int low = 0, high = len - 1;
while (low < high) {//排序趟数。排序区间[low,high]。
int flag = 0;//标志点
for (int i = low; i < high; ++i) {//第一次正向冒泡
if (a[i] > a[i + 1]) {
swap(a[i], a[i + 1]);
flag = i;
}
}
high = flag;//表示标志点之后的元素已经排好,区间右值缩小。
for (int i = high; i > low; --i) {//第2次反向冒泡
if (a[i] < a[i - 1]) {
swap(a[i], a[i - 1]);
flag = i;
}
}
low = flag;//表示断点之前的元素已经排好,区间左值缩小。
}
}
int main() {
int a[] = { 520,0,1,9,56,100,1,85,5,3,6 };
int len = sizeof(a) / sizeof(a[0]);
bubbleSort(a, len);
for (int i = 0; i < len; ++i) {
cout << " " << a[i];
}
getchar();
return 0;
}
//运行结果:
0 1 1 3 5 6 9 56 85 100 520
将数组分为已排序和待排序两个区间,即有序区和无序区。每次从无序区中选出最小/最大的元素与无序区的第一个元素(即有序区尾部)交换位置,直到无序区只剩一个元素,排序完成。
1 将数组划分为有序区和无序区。(开始有序区为空,无序区[0,n-1])
2 在无序区中找出最大的那个元素,与该区间第一个元素交换。(有序区元素个数加1,无序区减1)
3 重复步骤2 n-1次,排序完成。(排序趟数为n-1,最后一个元素无需排序)
时间复杂度最稳定,无论什么时候都为:T(n)=0(n^2)
#include
using namespace std;
void swap(int &a, int &b) {
int tmp = a;
a = b;
b = tmp;
}
void selectSort(int a[], int len) {
for (int i = 0; i < len - 1; ++i) {
int minIndex = i;
for (int j = i; j < len; ++j) {//找出无序区最小元素的下标
if (a[j] < a[minIndex]) {
minIndex = j;
}
}
swap(a[i], a[minIndex]);//无序区第一个元素与最小值交换。
}
}
int main() {
int a[] = { 5,89,562,4,2,0,56512,4512,5 };
int len = sizeof(a) / sizeof(a[0]);
selectSort(a, len);
for (int i = 0; i < len; ++i) {
cout << " " << a[i];
}
getchar();
return 0;
}
//运行结果:
0 2 4 5 5 89 562 4512 56512
插入排序跟选择排序很像,都分为有序区和无序区。但是选择排序是每次都从无序区中选出最小元素插入到有序区末尾,而插入排序是直接将数组的第一个元素作为有序区的第一个元素,每次都拿出无序区第个一元素插入到有序区合适的位置上,直到无序区为空,排序完成。
1 将数组分为有序区和无序区,有序区0,无序区[1,n-1];
2 取下无序区第一个元素,保存其值。
3有序区中元素从后往前与新元素比较,如果新元素更小,旧元素往后移。
3 重复步骤3,直到新元素大于或等于旧元素,将新元素插入该元素之后。
4 重复步骤234, n-1次,排序完成。
最好:T(n)=o(n),数组元素正序排列
最坏:T(n)=o(n^2)数组元素反序排列
平均:T(n)=o(n^2)
#include
using namespace std;
void sertSort(int a[], int len) {
for (int i = 1; i < len; ++i) {
int key = a[i];//保存无序区第一个元素为key
int j = i - 1;
while (!(j <0) && a[j] > key) {//新元素在有序区寻找位置
a[j + 1] = a[j];
j--;
}
a[j+1] = key;
}
}
int main() {
int a[] = {5,45,1,3,0,99,2,10 };
int len = sizeof(a) / sizeof(a[0]);
sertSort(a, len);
for (int i = 0; i < len; ++i) {
cout << " " << a[i];
}
getchar();
return 0;
}
//运行结果:
0 1 2 3 5 10 45 99
新元素在插入到有序区时,使用二分法查找位置而非一个一个依次查找。
#include
using namespace std;
void sertSort(int a[], int len) {
for (int i = 1; i < len; ++i) {
int key = a[i];
int left = 0, right = i-1;
while (!(left > right)) {//在区间内查找位置
int middle = (left + right) / 2;
if (a[middle] > key)
right = middle-1;
else left = middle+1;
}
for (int j = i - 1; !(j1] = a[j ];
}
a[left] = key;//left为新元素要插入的位置。
}
}
int main() {
int a[] = {5,45,1,3,0,99,2,10 };
int len = sizeof(a) / sizeof(a[0]);
sertSort(a, len);
for (int i = 0; i < len; ++i) {
cout << " " << a[i];
}
getchar();
return 0;
}
//运行结果:
0 1 2 3 5 10 45 99
希尔排序是以某个增量h为步长跳跃分组进行插入排序,由于增量是一个从h逐渐缩小至1的过程,所以又称缩小增量排序。
其核心在于间隔序列设定,即增量的设定,这也是也插入排序的本质区别。插入排序始终增量为1。
最佳增量:k趟排序增量步长为(2^k)-1,即增量序列(2^k)-1,…15,7,3,1
1确定增量序列(t1,t2…tk)ti>tj,tk=1;
2按增量序列个数k分成k趟排序
3每趟排序按对应增量ti,将序列分割成若干子序列,分别进行直接插入排序。
希尔排序实际上是将一维数组分成具有不同列数的二维数组(对应方式,a[i]->a[i/h][i%h])。在每一趟排序中对每一列进行插入排序。列宽也就是增量,增量减小也就是列数减小。
以排序数组a[]={10,8,4,3,1,5,7,9,2,6},增量分别为5,2,1为例:
#include
using namespace std;
void swap(int&a, int&b) {
int tmp = a;
a = b;
b = tmp;
}
void shellSort(int a[], int len) {
int gap = len ;
while (gap = gap / 2) {//增量
cout << "每列待排序元素:"<for (int i = gap; i < len; i++) {
cout << i << " ";
int key = a[i];//待排序元素
int j = i - gap;
for (; j+1>0&&a[j ] > key; j -= gap) {//插入排序
a[j + gap] = a[j];
}
a[j + gap] = key;
}cout << endl;
cout << "增量" << gap << "的排序结果:" << endl;
for (int i = 0; i < len; ++i) {
cout << " " << a[i];
}cout <int main() {
int a[] = { 10,8,4,3,1,5,7,9,2,6};
int len = sizeof(a) / sizeof(a[0]);
shellSort(a, len);
cout << "排序最终结果:";
for (int i = 0; i < len; ++i) {
cout << " " << a[i];
}
getchar();
return 0;
}
采用递归和分治的思想,首先将数组二分成多个子序列,然后两两子序列进行比较与合并,使其合并为一个完全有序的序列。不断进行比较与合并,使子序列们最终合并为一个完整有序的序列。
1将数组二分为多个子序列
2子序列进行排序(注:只有1个元素的序列本身就已经是排序好的序列)
3排序好的子序列间进行比较与合并。
3子序列完全合并为一个有序序列
最佳:o(n)
最坏:o(nlogn)
平均:o(nlogn)
#include
using namespace std;
void merge(int a1[], int na1, int a2[], int na2) {
int tmp[1000];
int t = 0, i = 0, j = 0,k=0;
while( i < na1&&j < na2){
if (a1[i] > a2[j]) {
tmp[t++] = a2[j++];
}
else tmp[t++] = a1[i++];
}
while (i < na1) {
tmp[t++] = a1[i++];
}
while (j < na2) {
tmp[t++] = a2[j++];
}
while(kint a[],int len) {
if (len>1) {
int mid = len / 2;
int *a1 = a;
int na1 = mid;
int *a2 = a + mid ;
int na2 = len - mid;
mergeSort(a1, na1);
mergeSort(a2, na2);
merge(a1, na1, a2, na2);
}
}
int main() {
int a[] = {0,7,5,2,1,3,8,4,6,9 };
int len = sizeof(a) / sizeof(a[0]);
mergeSort(a,len);
for (int i = 0; i < len; ++i) {
cout << " " << a[i];
}
getchar();
return 0;
}
分治和递归的应用。选取数组中一个元素为基准(pivot)P,对数组进行排序,使得比P大的元素都在P的右边,比P小的元素在P的左边。然后对以P为分界点的左右子串递归进行快排。
1从数列中选取一个元素作为基准;
2进行分区操作(partition)。重新排列数列,使得比基准小的元素都在其左边,比基准小的元素都在它右边,即分为左子列,基准,右子列。
3对左右子列递归进行快排。
最好:o(nlogn)
最坏:o(n^2)
平均:o(nlogn)
#include
using namespace std;
void swap(int &a, int &b) {
int tmp = a;
a = b;
b = tmp;
}
void quickSort(int a[], int low, int high) {
if (low < high) {
int i = low - 1;
int j = low;
int key = a[high];//基准
for (int j = low; j <= high; ++j) {//使比基准小或等于基准的元素前移。
if (a[j] <=key) {
++i;
swap(a[i], a[j]);
}
}
quickSort(a, low, i - 1);
quickSort(a, i + 1, high);
}
}
int main() {
int a[] = { 0,4,1,2,3,0,0,0 };
int len = sizeof(a) / sizeof(a[0]);
quickSort(a, 0, len - 1);
for (int i = 0; i < len; ++i) {
cout << " " << a[i];
}
getchar();
return 0;
}
#include
using namespace std;
void quickSort(int a[], int low, int high) {
if (low >= high)
return;
int i, j, key;
i = low;
j = high;
key = a[low];
while (i < j) {
while (i=key) {
j--;
}
a[i] = a[j];
while (i < j&&a[i] <=key) {
i++;
}
a[j] = a[i];
}
a[i] = key;//基准到位
quickSort(a, low, i - 1);
quickSort(a, i + 1, high);
}
int main() {
int a[] = { 0,4,1,2,3,0,0,0 };
int len = sizeof(a) / sizeof(a[0]);
quickSort(a, 0, len - 1);
for (int i = 0; i < len; ++i) {
cout << " " << a[i];
}
getchar();
return 0;
}
堆排序是将数组构建成大顶堆,即根节点是数组中最大元素,将根节点与堆底最后一个元素交换,使得最大值排到末尾,即已排序好。将剩下的n-1个元素重新调整为大顶堆,在堆顶/根节点处得到第二大的值,与堆底最后一个元素交换,便又排序好一个元素。
1将数组构建成大顶堆
2交换堆顶元素和堆底元素
3调整堆,使其重新成为大顶堆
4重复步骤2和步骤3 n-1次,排序完成。n为数组长度。
最好、最坏,平均都为o(nlogn)
#include
using namespace std;
void swap(int &a, int &b) {
int tmp = a;
a = b;
b = tmp;
}
void heap(int a[],int node,int len) {
int nodecopy = a[node];
for (int i = 2 * node + 1; i < len; i = 2 * i + 1) {//遍历node所有下属节点
if(i + 1 < len&&a[i + 1] > a[i])//如果右子树节点存在且右子树节点大于左子树,那么将下标移到右子树位置
i++;
if (a[i] > a[node]) {
a[node] = a[i];
node = i;
}
}
a[node] = nodecopy;//确定a[node]最终位置
}
void heapSort(int a[],int len) {
for (int node = len / 2 - 1; node >= 0; --node) {
heap(a, node, len);
}
for (int i = len-1;i>0; --i) {
swap(a[0], a[i]);
heap(a, 0, i);
}
}
int main() {
int a[] = { 7,0,1,2,8,5,9, };
int len = sizeof(a) / sizeof(a[0]);
heapSort(a, len);
for (int i = 0; i < len; ++i) {
cout << " " << a[i];
}
getchar();
}
非比较排序。计数排序是利用哈希原理,记录元素出现的频次。在统计结束后可以直接遍历哈希表,将数据填回数据空间。由于是空间换时间,所以适合对数据范围集中的数据使用。而且由于用数组下标表示,只适合只有正整数,0的数据。
1统计数组中元素出现的频次并找出极值。
2填充原数组。
T(n)=O(n+k)
注:k为范围,即新开辟的数组大小
#include
using namespace std;
void countSort(int a[], int len) {
int low = a[0], high = a[0];
for (int i = 0; i < len; ++i) {//找范围
low = a[i] < low ? a[i] : low;
high = a[i] > high ? a[i] : high;
}
int range = high - low+1;
int *count = new int[range];
memset(count, 0, sizeof(int)*(range));
for(int i=0;i//统计频次
count[a[i]-low]++;
}
for (int i = 0,j=0; i < range; ++i) {//赋值
while (count[i]--) {
a[j++] = i+low;
}
}
}
int main() {
int a[] = { 2, 2, 3, 8, 7,0,4};
int len = sizeof(a) / sizeof(a[0]);
countSort(a, len);
for (int i = 0; i < len; ++i) {
cout << " " << a[i];
}
getchar();
return 0;
}
非选择排序。将元素依次按低位到高位进行分类排序(个位,十位,百位…)与收集。即分别排序,分别收集。
注:适用范围,最好小于1000;数字为0或正整数。
1取得数组中最大位数。
2从低位开始,对数组进行排序。
3将排序好的元素复制到原始数组。
4重复2、3步骤到最高位。排序完成。
最好、最差、平均均为O(n*k)注:k为位数。
#include
#include
using namespace std;
int getnum(int a[], int len) {
int max = a[0];
int num = 1;
for (int i = 1; i < len; ++i) {
max=a[i] > max ? a[i] : max;
}
while (max /= 10) {
num++;
}
return num;
}
void radixSort(int a[], int len) {
int num = getnum(a, len);//获得位数
vector<vector<int>>radix(10);
for(int k=0;kfor (int i = 0; i < len; ++i) {//存放元素
int t = int(a[i] / pow(10, k))%10;
radix[t].push_back(a[i]);
}
vector<vector<int> >::iterator p;
vector<int>::iterator q;
int i = 0;
for (p = radix.begin(); p != radix.end(); ++p) {//取出元素
for (q = (*p).begin(); q !=(*p).end(); ++q) {
a[i++] = *q;
}
}
for (int i = 0; i < 10; ++i) {//清空容器中元素
if(!radix[i].empty())
radix[i].clear();
}
}
}
int main() {
int a[] = { 0,1,89,4,45,41,7,9,0,52,80,100 };
int len = sizeof(a) / sizeof(a[0]);
radixSort(a, len);
for (int i = 0; i < len; ++i) {
cout << " " << a[i];
}
getchar();
return 0;
}
顾名思义,桶排序就是把同类元素放在相同的桶里,然后又对桶内元素递归调用桶排序本身,是分治和递归的典型。
计数排序,基数排序以及桶排序的比较:
三者都出现了桶的概念。
计数排序是一个桶只存单一值,
基数排序是根据元素的位数来往桶里存放数据,
而桶排序是存储一定范围的数据。
1确定桶的数量和桶区间
2遍历列表把元素放到对应桶里
3重复步骤2,即对桶中元素递归调用桶排序。
4把排序好的元素放回原列表,直到排序完成
平均:T(n)=o(n^2)
#include
#include
using namespace std;
int getRange(vector<int> a, int begin, int end,int &min,int &max) {//获得数组元素范围和极值
for (int i = begin + 1; i <= end; ++i) {
min = a[i] < min ? a[i] : min;
max = a[i] > max ? a[i] : max;
}
return max - min+1;
}
void bucketSort(vector<int>&a, int begin, int end) {//容器做参数时用引用传递来调换元素顺序,
if (end - begin <1)
return;//递归出口
int min, max;min=max = a[0];
int range = getRange(a, begin, end, min, max); //获得元素范围和极值
int bucketNum = 5;//本次定义了5个桶
int gap = range / 5+1;//设定桶区间
vector <vector<int > > bucket(bucketNum);//用二维容器来装桶
for (int i = begin; i <= end; ++i){
cout << "桶编号: " << (a[i] - min) / gap << "放入元素:" << a[i] << endl;
bucket[(a[i]-min )/ gap] .push_back( a[i]);//元素放在不同的桶里
}
for (int i = 0; i < bucketNum; ++i) {
bucketSort( bucket[i],0,bucket[i].size()-1);//对桶里的元素递归调用桶排序
}
for (int i = 0,j=0; i < bucketNum; ++i) {
if (!bucket[i].empty()) {//桶非空判断
for (vector<int>::iterator p = bucket[i].begin(); p!=bucket[i].end(); ++p) {//桶里排序好的元素放回原容器a
a[j++] = *p;
}
}
}
cout << endl;
}
int main() {
vector<int>a;
for (int i = 0,j=100; i<20; ++i) {
a.push_back(j--);
}
bucketSort(a, 0, a.size()-1);
cout << "排序结果:" << endl;
for (int i = 0; icout << " "<return 0;
}