上篇是简单排序,简单排序的思维比较简单,实际应用的时候并不如人意,所以基于简单的排序发展了以其为基础的排序方法.
希尔排序
希尔排序就是根据插入排序修改的
基本思路:分间隔进行排序,跳出只对相邻两数进行比较的局限,可以尽量减少逆序对,间隔的选择可以有n/2,每次除以2,也有其他间隔选择,比如Sedgewick增量
{587521,260609,146305,64769,36289,16001,8929,3905,2161,929,505,209,109,41,19,5,1,0 };
以及Hibbard增量
{ 32767,16383,8191,4095,2047,1023,511,255,127,63,31,15,7,3,1,0 };
代码实现
//希尔排序原型 在插排中增加一个改变间隔的外循环
#include <stdio.h>
#include <iostream>
using namespace std;
void paixv(int arr[], int n);
int main() {
int n, m;
int arr[100001];
cin >> n;//n是整数个数
//插入数据
for (int i = 0; i < n; i++) {
cin >> m;
arr[i] = m;
//cout << arr[i] << " ";测试
//cout << endl;
}
paixv(arr, n);
return 0;
}
void paixv(int arr[], int n) {
int i, j, temp;
for (int d = n / 2; d > 0; (d /= 2) ) {//控制间隔
for (i = d; i < n; i++) {//从数组第二项开始
temp = arr[i];
for (j = i; j >= d && temp < arr[j - d]; j-=d) {
//第一次循环时i=J,循环退出时j=0或者temp比arr[j-1]大
arr[j] = arr[j - d];
}
arr[j] = temp;
}
}
//输出结果
for (i = 0; i < n; i++) {
cout << arr[i];
if (i < n - 1) {
printf(" ");
}
}
}
增量希尔排序(函数声明和上一个相同
#include <stdio.h>
#include <iostream>
using namespace std;
void paixv(int arr[], int n);
int main() {
int n, m;
int arr[100001];
cin >> n;//n是整数个数
//插入数据
for (int i = 0; i < n; i++) {
cin >> m;
arr[i] = m;
//cout << arr[i] << " ";测试
//cout << endl;
}
paixv(arr, n);
return 0;
}
//Hibbard增量希尔排序
void paixv(int arr[], int n) {
int i, j, temp, k = 0;
//Sedgewick增量
//int add[] = { 587521,260609,146305,64769,36289,16001,8929,3905,2161,929,505,209,109,41,19,5,1,0 };
//Hibbard增量
int add[] = { 32767,16383,8191,4095,2047,1023,511,255,127,63,31,15,7,3,1,0 };
for (int d = add[k]; d > 0; d = add[++k]) {
for (i = d; i < n; i++) {//从数组第二项开始
temp = arr[i];
for (j = i; j >= d && temp < arr[j - d]; j -= d) {
//第一次循环时i=J,循环退出时j=0或者temp比arr[j-1]大
arr[j] = arr[j - d];
}
arr[j] = temp;
}
}
for (i = 0; i < n; i++) {
cout << arr[i];
if (i < n - 1) {
printf(" ");
}
}
}
堆排序
思路:把数据按最大堆调整,每一次将最大节点和根节点互换后,总节点数减一再调整为最大堆,结束后数据按从小到大的顺序排列
#include <stdio.h>
#include <iostream>
using namespace std;
void paixv(int arr[], int n);
int main() {
int n, m;
int arr[100001];
cin >> n;//n是整数个数
//插入数据
for (int i = 0; i < n; i++) {
cin >> m;
arr[i] = m;
//cout << arr[i] << " ";测试
//cout << endl;
}
paixv(arr,n);
return 0;
}
void swap(int *a, int *b) {
int t = *a; *a = *b; *b = t;
}
//传进数组 根节点 节点个数
void PercDown(int arr[],int p,int n) {
// 将N个元素的数组中以A[p]为根的子堆调整为最大堆
int parent, child;
int x;
x = arr[p];//存放根节点的值
for (parent = p; (parent * 2 + 1) < n; parent = child) {
child = parent * 2 + 1;//求出子节点
if ((child != n - 1) && (arr[child] < arr[child + 1])) {
child++;//child指向左右子节点中的较大者
}
if (x >= arr[child])break;
else
arr[parent] = arr[child];//当父亲节点的值小于儿子节点 令父节点等于儿子节点 进入下一循环(往下过滤
}
arr[parent] = x;
}
//堆排序 (最大堆
void paixv(int arr[], int n) {
int i;
//首先建立最大堆
for (i = n / 2 - 1; i >= 0; i--) {
PercDown(arr, i, n);
}
//对大顶堆排序
//删除大顶堆最大节点
for (i = n - 1; i > 0; i--) {
//arr[i]为数组中最后一个元素,也是最小的元素,与根节点(max)交换
//排除最后一个元素(也就是最大值),然后重构大顶堆
swap(&arr[0], &arr[i]);
PercDown(arr, 0, i);//将对n-1个节点进行重构,第n个节点被固定
}//到循环结束,数组按从小到大的顺序排列
//输出函数
for (i = 0; i < n; i++) {
cout << arr[i];
if (i < n - 1) {
printf(" ");
}
}
}
归并排序
思路:分而治之,将序列分成多个长度为2的序列,比较排序后进行合并,重复操作直到结束.
有两种实现方法,一是递归,二是循环.递归比较容易理解,
下面给出递归的代码实现
#include <stdio.h>
#include <iostream>
#include <math.h>
using namespace std;
void paixv(int arr[], int n);
int main() {
int n, m;
int arr[100001];
cin >> n;//n是整数个数
//插入数据
for (int i = 0; i < n; i++) {
cin >> m;
arr[i] = m;
//cout << arr[i] << " ";测试
//cout << endl;
}
paixv(arr,n);
return 0;
}
//合并函数
void merge(int arr[], int tmpa[], int L, int R, int rightend) {
//L:左边数组初始位置.R:右边初始 rightend:右边结尾
int leftend, tmp, numElements, i;
leftend = R - 1;
tmp = L;//记录初始位置
numElements = rightend - L + 1;
while (L <= leftend && R <= rightend) {
if (arr[L] <= arr[R])
tmpa[tmp++] = arr[L++];
else tmpa[tmp++] = arr[R++];
}
while (L <= leftend) tmpa[tmp++] = arr[L++];
while (R <= rightend) tmpa[tmp++] = arr[R++];
//有序数组构建完毕 将有序数组导入旧数组
for (i = 0; i < numElements; i++, rightend--) {
arr[rightend] = tmpa[rightend];
}
}
//排序函数
void MSort(int arr[],int tmpa[],int L,int rightend) {
int center;
if (L < rightend) {
center = (L + rightend) / 2;//求中点,分开两数组
MSort(arr, tmpa, L, center);
MSort(arr, tmpa, center + 1, rightend);
merge(arr, tmpa, L, center + 1, rightend);
}
}
//归并排序 分而治之
void paixv(int arr[], int n) {
int i;
int *tmpa;
tmpa = (int *)malloc( n*sizeof(int)+1);
if (tmpa != NULL) {//当空间分配成功
MSort(arr, tmpa, 0, n-1);
free(tmpa);
}
else printf("空间不足");
//cout << "arr[0]=" << arr[0] << endl;
//输出函数
for (i = 0; i < n; i++) {
cout << arr[i];
if (i < n - 1) {
printf(" ");
}
}
}
循环的代码实现
循环和递归最大的不同就是循环需要考虑到循环结束时的条件究竟是什么.
void merge(int arr[], int tmpa[], int L, int R, int rightend) {
int leftend, numElements, tmp;
leftend = R - 1;
tmp = L;//记录初始位置信息
numElements = rightend - L + 1;
while (L <= leftend && R <= rightend) {
if (arr[L] < arr[R]) tmpa[tmp++] = arr[L++];
else tmpa[tmp++] = arr[R++];
}
//循环结束时存在一个一边空了 一边还有数据
while (L <= leftend) tmpa[tmp++] = arr[L++];
while (R <= rightend) tmpa[tmp++] = arr[R++];
/* 这部循环在循环实现的归并排序中可以不用做 但是做不做结果都相同
for (int i = 0; i < numElements; i++, rightend--) {
arr[rightend] = tmpa[rightend];
}*/
}
//循环实现
//排序
void MSort(int arr[], int tmpa[], int n, int length) {
int i;
for (i = 0; i <= n - 2 * length; i += 2 * length) {
merge(arr, tmpa, i, i + length, i + 2 * length - 1);
}
if (i + length < n)
merge(arr, tmpa, i, i + length, n - 1);
else
for (int j = i; j < n; j++) tmpa[j] = arr[j];
}
//归并排序 循环实现
void paixv(int arr[], int n) {
int i;
int length = 1;//有序序列长度 初始为1 每次乘2
int *tmpa;
tmpa = (int *)malloc(n * sizeof(int));
if (tmpa != NULL) {//当空间分配成功
while (length < n) {
MSort(arr, tmpa, n, length);
length *= 2;
MSort(tmpa, arr, n, length);
length *= 2;
}
free(tmpa);
}
else printf("空间不足");
//cout << "arr[0]=" << arr[0] << endl;
//输出函数
for (i = 0; i < n; i++) {
cout << arr[i];
if (i < n - 1) {
printf(" ");
}
}
}