相邻两个元素进行大小比较,如果前一个比后一个大,就交换
在冒泡排序的过程中,促进了大的数往后去,小的数往前去
1.从头开始将相邻两个元素进行大小比较,如果前一个比后一个大,就交换,直到全部比较完成,固定最右边的值(此时固定的就是最大的值)
2.重复操作1,直到最后剩一个元素了,排序结束
void BubbleSort(int a[],int length) {
if (a == NULL || length <= 0) return;
for (int i = 0; i < length -1; i++) {
for (int j = 0; j < length -1-i; j++) {
if (a[j] > a[j + 1]) {
a[j] = a[j] ^ a[j + 1];
a[j + 1] = a[j] ^ a[j + 1];
a[j] = a[j] ^ a[j + 1];
}
}
}
}
void Print(int a[], int length) {
if (a == NULL || length <= 0) return;
for (int i = 0; i < length; i++) {
cout << a[i] << " ";
}
cout << endl;
}
int main() {
int a[] = { 3,11,9,2,16,8,20,28,31,44 };
BubbleSort(a,sizeof(a)/sizeof(a[0]));
//将排好序的数组进行输出
Print(a,sizeof(a)/sizeof(a[0]));
cout << endl;
return 0;
}
我们发现第二趟完事之后,这个数组就已经有序了,我们就不需要再进行下面几次的操作了
我们在进行冒泡排序时,每一趟都找到最后交换的位置,那下一趟的右端范围到最后交换位置之前就可以了(用这种方法优化之后冒泡排序所需要的次数一般都会大幅减少),如果没有交换发生,就说明此数组已经有序了
当缩小范围(减少趟数)的时候,也就是对第一个循环进行的操作,我们控制的是左端点,右端点不动,并且通过数学的计算得出每次左端点要变为 数组长度 - 最后交换的位置在数组中的下标所得的值
#include
using namespace std;
void BubbleSort(int a[],int length) {
if (a == NULL || length <= 0) return;
int flag = 0;
for (int i = 0; i < length -1; i++) {//总趟数
flag = 0;
for (int j = 0; j < length -1-i; j++) {//每趟的范围
if (a[j] > a[j + 1]) {
a[j] = a[j] ^ a[j + 1];
a[j + 1] = a[j] ^ a[j + 1];
a[j] = a[j] ^ a[j + 1];
flag = j + 1;
}
}
if (flag == 0) break;//如果没有交换发生,就说明此数组已经有序了,结束操作
i = length - flag - 1;//这里多减了一个一是因为每一次循环i都会自增1
}
}
void Print(int a[], int length) {
if (a == NULL || length <= 0) return;
for (int i = 0; i < length; i++) {
cout << a[i] << " ";
}
cout << endl;
}
int main() {
int a[] = { 3,9,2,11,8,16,20,28,31,44 };
BubbleSort(a, sizeof(a) / sizeof(a[0]));
//将排好序的数组进行输出
Print(a, sizeof(a) / sizeof(a[0]));
return 0;
}
每趟找最值放入到对应位置上(整体要保持一致性要找最大就只能找最大,反则亦然)
1.假设数组的第一个元素就是最大值,此时最大值的下标为0,然后依次进行比较,如果遇到大的数,那么最大值的下标进行更新,直到所有的元素都比较完成了,将最大值与数组最右边的数进行互换(如果最大值的下标就是数组最右边元素的下标,那么就不交换),固定数组最右边的元素(之后此位置就不参与比较了)
2.重复操作1,直到数组中只剩一个元素了,排序结束
#include
using namespace std;
void SelectSort(int a[], int length) {
if (a == NULL || length <= 0) return;
int i;
int j;
for ( i= 0; i < length - 1; i++) {
int MaxIndex = 0;
for (j = 0; j < length - i-1; j++) {
//与最值进行比较
if (a[MaxIndex] <= a[j]) {
MaxIndex = j;
}
}
//将最值放到数组的最右边(通过交换实现的)
if (MaxIndex != j-1) {//如果最大值的下标不是数组最右边元素的下标,进行交换
a[MaxIndex] = a[j-1] ^ a[MaxIndex];
a[j-1] = a[j-1] ^ a[MaxIndex];
a[MaxIndex] = a[j-1] ^ a[MaxIndex];
}
}
}
void Print(int a[], int length) {
if (a == NULL || length <= 0) return;
for (int i = 0; i < length; i++) {
cout << a[i] << " ";
}
cout << endl;
}
int main() {
int a[] = { 9,1,3,7,98,58,15,4,45,5,2131,23 };
SelectSort(a, sizeof(a) / sizeof(a[0]));
//将排好序的数组进行输出
Print(a, sizeof(a) / sizeof(a[0]));
return 0;
}
将待排序数据分为两部分,一部分无序,一部分有序,将无序元素依次插入到有序中去,完成排序
1.元素很少的时候(元素小于16时就认为元素很少)
2.每个元素在排序前的位置距排好序的最终位置不远的时候
1.申请两个标记变量,一个指向有序的最后一个,另一个指向无序的第一个元素(最开始时我们把数组第一个元素看成是有序的,其他的元素是无序的,所以最开始有序的标记变量指向的是数组的第一个元素,无序的标记变量指向的是数组的第二个元素),再申请一个临时变量,临时变量用来存每一次要进行操作的无序元素(防止无序的这个元素被覆盖)
2.插入无序元素
(1)遍历无序的元素
(2)插入每一个无序的元素时进行倒叙遍历有序数组
如果遍历到的元素的值小于无序值,那么无序值就放到当前元素的下一个位置上继续遍历无序的元素(此时无序的标记变量向后移一位,有序元素的标记指向的位置变为无序标记的指向的位置的前一位,)
如果遍历到的元素的值大于无序值,那么当前元素后向右移一位,继续倒叙遍历有序数组
如果遍历到了边界(也就是数组的起始元素),那么该无序元素变为有序元素的第一个,继续倒叙遍历有序数组
#include
using namespace std;
void InsertSort(int a[],int length) {
int yesIndex = 0;
int noIndex = 1;
int temp = a[noIndex];
//依次插入无序
while (noIndex < length&& yesIndex>=-1) {
if (yesIndex == -1) {
a[0] = temp;
noIndex++;
yesIndex = noIndex - 1;
temp = a[noIndex];
}
if (a[yesIndex] > temp) {
a[yesIndex+1] = a[yesIndex];
yesIndex--;
}
else {
a[yesIndex+1] = temp;
noIndex++;
yesIndex = noIndex - 1;
temp = a[noIndex];
}
}
}
int main() {
int a[] = { 5,1,856,6,32,14,7,9,77 };
InsertSort(a, sizeof(a) / sizeof(a[0]));
for (int i = 0; i < sizeof(a) / sizeof(a[0]); i++) {
cout << a[i] << " ";
}
return 0;
}
希尔排序可以理解成放大版的插入排序
1.定间隔(造图时用的时二分,实际生产应用中会和斐波那契数列有关系这里我们用二分来确定间隔)
(1)初始定间隔:我们用数组的总长度除以2得到的结果向下取整,然后我们以此结果作为间隔,执行第二步
(2)重新定间隔:间隔变为间隔减半的结果向下取整(这里除2了,但是真正使用的时候不是除2的)执行第二步
2.将数组中的元素根据间隔进行分组(例如如果所得的间距为6,那么下标为0的就和下标为6的一组,下标为1的就和下标为7的一组)
3.各组内进行插入排序
4.如果排序还没有完成的话,(我们需要重新定间隔,重新分组),从第一步的(2)重新开始
如果排序完成,结束
希尔排序的时间复杂度是无法计算的,一般会有一个范围在n的1.2次方到n的1.5次方,我们一般取n的1.3次方
核心思想基于非比较
数据是有范围的(分配比较密集),数据重复出现次数比较多
1.先遍历一遍数组找到该数组的最大值和最小值
2.申请一个计数数组,它的长度是上一步找的最大值和最小值的差值+1,并将计数数组所有元素初始化为0
3.遍历原数组,进行计数
遍历到的每一个元素的值减去步骤1中获得的最小值作为计数数组的下标,然后计数数组的对应下标进行+1操作,以此实现计数
4.放回操作
遍历计数数组,当遍历到的计数数组的元素不为0时,将当前元素所在的下标加上步骤1中获得的最小值得到的值依次放回到原数组中(覆盖操作)
#include
using namespace std;
void CountingSort(int a[],int length) {
//找到最大值和最小值
int min = a[0];
int max = a[0];
for (int i = 0; i < length; i++) {
if (min > a[i]) {
min = a[i];
}
if (max < a[i]) {
max = a[i];
}
}
//申请array数组用来计数
int* array = (int*)malloc(sizeof(int) * (max - min + 1));
memset(array, 0, sizeof(int)*(max - min+1));
//用array数组进行计数
for (int i = 0; i < length; i++) {
array[a[i] - min]++;
}
//开始排序,放回
int t = 0;
for (int i = 0; i < max - min + 1 ; i++) {
while (array[i]) {
a[t] = i + min;
t++;
array[i]--;
}
}
}
void Print(int a[], int length) {
if (a == NULL || length <= 0) return;
for (int i = 0; i < length; i++) {
cout << a[i] << " ";
}
cout << endl;
}
int main() {
int a[] = { 2,9,4,1,8,9,3,1,4,7,6,9,4 };
CountingSort(a, sizeof(a) / sizeof(a[0]));
Print(a, sizeof(a) / sizeof(a[0]));
return 0;
}
因为如果元素时结构体的话,比如学生这个结构体,包括学生分数,学生姓名,学号等等,那么我们没进行优化之前只能把学生分数进行排序,那么就会出现信息不匹配的问题,所以我们要进行优化,优化的方法就是再申请一个数组,在这个数组中进行排序(此数组复制了一份原来数组中的元素),然后将排好序的元素放回到原数组中
1.先遍历一遍数组找到该数组的最大值和最小值
2.申请一个计数数组,它的长度是上一步找的最大值和最小值的差值+1,并将计数数组所有元素初始化为0
3.遍历原数组,进行计数
遍历到的每一个元素的值减去步骤1中获得的最小值作为计数数组的下标,然后计数数组的对应下标进行+1操作,以此实现计数
4.遍历一遍计数数组,计算名次
计数数组中的每一个元素都跟前一个元素相加,依次来得到名次
(计数数组的每一个元素记录的名次是当前元素最后一次出现时的排名)
5.申请一个新数组,在这个新数组中进行排序
倒叙遍历元素组,每一个元素减去第一步中的最小值获得的值找到每一个元素在计数数组中的位置,然后获得该元素的名次,从而找到它在新数组中的位置,最后复制这个元素到该位置(注意,每次获得名次后,该名次应进行减1操作,为了下一次有相同的值的元素,能够继续成功排序)
6.排好序的元素,放回到原数组中
7.释放申请的空间
#include
using namespace std;
void CountingSort(int a[],int length) {
//找到最大值和最小值
int min = a[0];
int max = a[0];
for (int i = 0; i < length; i++) {
if (min > a[i]) {
min = a[i];
}
if (max < a[i]) {
max = a[i];
}
}
//申请array数组用来计数
int* array = (int*)malloc(sizeof(int) * (max - min + 1));
memset(array, 0, sizeof(int)*(max - min+1));
//用array数组进行计数
for (int i = 0; i < length; i++) {
array[a[i] - min]++;
}
//排名次
for (int i = 1; i < max - min + 1; i++) {
array[i] += array[i - 1];
}
//申请一个新空间
int* Temp = (int*)malloc(sizeof(int) * length);
memset(Temp, 0, sizeof(int) * (max - min + 1));
//倒叙遍历原数组,进行排序
for (int i = length - 1; i >= 0; i--) {
Temp[array[a[i] - min] - 1] = a[i];
array[a[i] - min]--;
}
//放回
for (int i = 0; i < length; i++) {
a[i] = Temp[i];
}
//释放
free(Temp);
Temp = NULL;
free(array);
array = NULL;
}
int main() {
int a[] = { 2,9,4,1,8,9,3,1,4,7,6,9,4 };
CountingSort(a, sizeof(a) / sizeof(a[0]));
for (int i = 0; i < sizeof(a) / sizeof(a[0]); i++) {
cout << a[i] << " ";
}
return 0;
}
找一个标准值,将比标准值小的都放在其左侧,比标准值大的,都放在其右侧,根据标准值将数据分割成两部分,两部分分别重复以上操作
1.选定标准值(标准值在某些算法里也叫枢轴),然后保存一下标准值,此处变为坑(注意这里我们选择数组的首元素作为标准值)
2.从右向左找比标准值小的,找到填入到前面的坑中,此处变为坑
从左向右找比标准值大的,找到之后填到后面的坑中,此处变为坑
直到从右向左和从左向右相遇的时候,此操作结束,此位置就是标准值的位置,放入标准值
3.根据标准值的部分,将这组数据分为两部分,左部分从起始位置到标准值左边的位置,右部分从标识值右边的位置到结束位置,然后将这两部分重新进行操作(注意如果某一部分只有一个元素或者没有元素那么就不用进行操作了,因为如果只有一个元素那它一定是有序的,而如果没有元素那就根本不需要进行排序)
#include
using namespace std;
int Sort(int arr[],int begin, int end) {
int temp = arr[begin];
while (begin< end) {
//从右向左找比标记值小的元素
while (begin < end) {
if (arr[end] < temp) {
//填入左侧坑
arr[begin] = arr[end];
begin++;
break;
}
end--;
}
//从左向右找比标记值大的元素
while (begin < end) {
if (arr[begin] > temp) {
//填入右侧坑
arr[end] = arr[begin];
end--;
break;
}
begin++;
}
}
//将标准值放入
arr[begin] = temp;
return begin;
}
void QuickSort(int arr[], int begin, int end) {
if (arr == NULL || begin >= end) return;
//标准值
int standard;
standard = Sort(arr, begin, end);
//拆分
QuickSort(arr, begin, standard - 1);
QuickSort(arr, standard + 1, end);
}
void Print(int a[], int length) {
if (a == NULL || length <= 0) return;
for (int i = 0; i < length; i++) {
cout << a[i] << " ";
}
cout << endl;
}
int main() {
int a[] = { 5,6,9,74,5,2,3,84,39,6,21,65,1,77 };
int begin = 0;
int end = sizeof(a) / sizeof(a[0]);
QuickSort(a, begin, end - 1);
//遍历
Print(a, sizeof(a) / sizeof(a[0]));
return 0;
}
较于瓦克填补法能够使程序编译期的所用时间变短
1.选定标准值(标准值在某些算法里也叫枢轴),然后保存一下标准值(注意这里我们选择数组的尾元素作为标准值)
2.申请一个小空间变量,存的是小于标准值元素(小空间)的最后一个下标(最开始的值是起始位置-1所得到的值)
3.从小空间最后一个下标,遍历,如果遍历到的元素比标准值小的话,那么元素与小空间最后一个元素的下一个元素进行交换,小空间变量进行+1操作,当遍历完所有元素(除最后一个元素)之后,最后一个元素(标准值)与小空间最后一个元素的下一个元素进行交换,小空间变量进行+1操作
4.根据标准值的部分,将这组数据分为两部分,左部分从起始位置到标准值左边的位置,右部分从标识值右边的位置到结束位置,然后将这两部分重新进行操作(注意如果某一部分只有一个元素或者没有元素那么就不用进行操作了,因为如果只有一个元素那它一定是有序的,而如果没有元素那就根本不需要进行排序)
#include
using namespace std;
int Sort(int arr[], int begin, int end) {
int small = begin - 1;
//遍历
for (begin; begin < end; begin++) {
//比较
if (arr[begin] < arr[end]) {
//小区间扩张
if (++small != begin) {
arr[small] = arr[small] ^ arr[begin];
arr[begin] = arr[small] ^ arr[begin];
arr[small] = arr[small] ^ arr[begin];
}
}
}
//放入
if (++small != end) {
arr[small] = arr[small] ^ arr[end];
arr[end] = arr[small] ^ arr[end];
arr[small] = arr[small] ^ arr[end];
}
return small;
}
void QuickSort(int arr[], int begin, int end) {
if (arr == NULL || begin >= end) return;
//标准值
int standard;
standard = Sort(arr, begin, end);
//拆分
QuickSort(arr, begin, standard - 1);
QuickSort(arr, standard + 1, end);
}
int main() {
int a[] = { 5,6,9,74,5,2,3,84,39,6,21,65,1,77 };
int begin = 0;
int end = 14;
QuickSort(a, begin, end - 1);
//遍历
for (int i = 0; i < 14; i++) {
cout << a[i] << " ";
}
return 0;
}
3选1(选起始位置,中间位置,结束位置,然后找到三者中的中间值作为标准值)
9选1(选九个位置,然后每三个再选一个中间值,然后得到的这三个中间值作为一组再在其中选一个中间值作为标准值)
将标准值进行聚集
如果遍历到的元素的值和标准值一样大的话,就放在最前/最后的位置,最后确定完标准值的位置之后,将这些元素放到标准值左侧/右侧,形成聚集
将多个有序数组进行合并
1.找到数组中间位置
2.根据中间位置进行拆分,使其变成有序的
左半部分从起始部分到中间位置
右半部分从中间向右一位的位置到结束位置
注意:某一部分中的元素只有一个时,那这一部分就是有序的
3.将两个有序的部分合并
注意:这里我们会申请数组,进行元素的处理,当全部处理完成后,我们把处理完的元素放入到原数组中
(1)从头开始遍历两个部分,进行比较,谁小谁先放进申请的数组中
(2)当有一个部分遍历完成后,看另外一部分是否遍历完成,没完成就将还没遍历的部分放进数组中
(3)把在申请的数组中处理完的元素放入到原数组中
#include
#include
#include
void Sort(int arr[],int res[],int begin, int end) {
if (begin == end) {
res[0] = arr[begin];
return;
}
int mid = (begin + end) >> 1;
int* res1 = (int*)malloc(sizeof(int) * (mid - begin + 1));
memset(res1, 0, sizeof(int) * (mid - begin + 1));
int* res2 = (int*)malloc(sizeof(int) * (end - mid));
memset(res1, 0, sizeof(int) * (end - mid));
Sort(arr, res1, begin, mid);
Sort(arr, res2, mid+1, end);
//合并
int i = 0;
int index1 = 0;
int index2 = 0;
int mid1 = mid;
while (begin <= mid1 && end >= mid + 1) {
if (res1[index1] < res2[index2]) {
res[i] = res1[index1];
i++;
begin++;
index1++;
}
else {
res[i] = res2[index2];
i++;
mid++;
index2++;
}
}
if (begin > mid1) {
for (int j = index2; j <= end-mid1+1; j++) {
res[i] = res2[j];
i++;
}
}
if (mid + 1 > end) {
for (int j = index1; j <= mid1; j++) {
res[i] = res1[j];
i++;
}
}
//释放空间
free(res1);
free(res2);
}
void MergeSort(int arr[],int begin,int end) {
if (arr == NULL||begin >= end) return;
//申请一个数组
int* a = (int*)malloc(sizeof(int) * (end - begin + 1));
memset(a, 0, sizeof(int) * (end - begin + 1));
//排序
Sort(arr,a,begin,end);
for (int i = begin; i <= end; i++) {
arr[i] = a[i];
}
free(a);
}
void Print(int a[], int length) {
if (a == NULL || length <= 0) return;
for (int i = 0; i < length; i++) {
printf("%d ", a[i]);
}
printf("\n");
}
int main() {
int a[] = { 1,57,56,3,23,4,65,123,19,46,88,5};
int begin = 0;
int end = sizeof(a) / sizeof(a[0]);
MergeSort(a, begin, end-1);
Print(a, end);
return 0;
}
堆排序可以动态维护一组数据内的最值
在整棵二叉树中,父亲和左右孩子三者中,父亲都是最大值,就被称为大顶堆
在整棵二叉树中,父亲和左右孩子三者中,父亲都是最小值,就被称为小顶堆
堆排序中我们用到了完全二叉树的一些逻辑,一颗完全二叉树按照从上到下从左至右从0开始编号(0~n-1),编号为i的节点
如果满足2*i+1<=n-1,则其有左孩子,否则没有
如果满足2*i+2<=n-1,则其有右孩子,否则没有
父亲节点的编号是0~2分之n-1
1.建初始堆(这里建大堆)
从数组的n/2-1到0依次调整各个父亲节点
每个父亲节点调整的方式:看父亲节点和左右孩子中的最大值进行比较,如果比最大值小的话,父亲节点的值就和左右孩子中那个较大的那个进行交换,然后那个进行交换的孩子节点变为新的父亲节点继续讨论,如果新的父亲节点没有左右孩子或者没有发生交换那么结束
2.进行排序
(1)堆顶元素与末尾元素进行交换
(2)调整堆顶
调整堆顶的方式和上面的调整方式相同
#include
void adjust(int arr[], int index, int length) {
if (index * 2 + 1 >= length && index * 2 + 2 >=length) {
return;
}
int weizhi = 0;
//看当前节点左孩子,和右孩子,取它们大的那个
int Max = 0;
//如果有左孩子和右孩子
if (index * 2 + 1 < length&& index * 2 + 2 < length) {
if (arr[index * 2 + 1] > arr[index * 2 + 2]) {
Max = arr[index * 2 + 1];
weizhi = index * 2 + 1;
}
else {
Max = arr[index * 2 + 2];
weizhi = index * 2 + 2;
}
}
//如果只有左孩子
if (index * 2 + 1 <= length && index * 2 + 2 > length) {
Max = arr[index * 2 + 1];
weizhi = index * 2 + 1;
}
//看这个值是否比当前节点大,大的话进行交换,然后讨论交换的那个节点
if (Max > arr[index]) {
arr[index] = arr[index] ^ arr[weizhi];
arr[weizhi] = arr[index] ^ arr[weizhi];
arr[index] = arr[index] ^ arr[weizhi];
adjust(arr, weizhi, length);
}
}
void HeapSort(int arr[],int length) {
if (arr == NULL || length <= 0) return;
//1.建初始堆
for (int i = length / 2 - 1; i >=0 ; i--) {
adjust(arr, i, length);
}
//2.排序
for (int i = length-1; i > 0; i--) {
//进行交换
arr[0] = arr[0] ^ arr[i];
arr[i] = arr[0] ^ arr[i];
arr[0] = arr[0] ^ arr[i];
adjust(arr, 0, i );
}
}
void Print(int a[], int length) {
if (a == NULL || length <= 0) return;
for (int i = 0; i < length; i++) {
printf("%d ", a[i]);
}
printf("\n");
}
int main() {
int arr[] = { 4, 6, 2, 1, 8, 78, 2123, 48, 49, 58, 11, 9 };
int length = sizeof(arr) / sizeof(arr[0]);
HeapSort(arr, length);
Print(arr, length);
return 0;
}
1.找到这组数据的最大值和最小值
2.确定分组方式
3.申请一个指针数组
4.进行入组(头插法放入节点)
5.组内进行排序
6.放回到原数组中
7.释放
1.MSD(高位优先)
2.LSD (低位优先)
1.找到这组数据中的最大值
2.计算最大值的位数
3.按位处理(这是个循环,循环的次数取决于位数)(这里用的是LSD)
(1)申请组(申请10组,因为十进制是0~9)
(2)拆位(先个位在十位再百位…)
(3)数据进行入组操作(数据根据 当前拆位(个位,十位,百位等)的值进行分组)
(4)将数据放回到原数组中
(5)释放申请组的空间
数值相同的元素,在排序前后,其相对位置未发生改变