一、交叉类排序
1、冒泡排序
基本思想
1、从后往前(或从前往后)两两比较相邻元素的值,如果前一个数大于后一个数,则交换位置,
2、直到比较完,把最小的值交换到第一个位置
3、进第二次冒泡排序,前一次确认的最小元素不用参与比较,
最大执行(n-1)次冒泡排序就能把所有的元素拍好
冒泡过程 | 初始 | 第一次 | 第二次 | 第三次 |
---|---|---|---|---|
数组开头 | 8 | 4 | 4 | 4 |
7 | 8 | 5 | 5 | |
5 | 7 | 8 | 7 | |
数组结尾 | 4 | 5 | 7 | 8 |
代码实现
步骤
1、随机生成10个元素
2、打印随机生成后的元素顺序
3、执行冒泡排序
3.1、需要两层冒泡排序,外层控制有序数的数数目(已经排好序的数字的个数),内层控制比较和交换
(优先写内部循环,再写外部循环)
4、打印排序后的元素顺序
// 交换函数
void swap(int &a, int &b){
int temp=a;
a=b;
b=temp;
}
// 冒泡排序
void BubbleSort(Elemtype *elem, int len){
int i,j;
// 哨兵
bool flog= false;
// i最多访问到len-1, 如果要是到第len次时,内层函数进不去,没有意义
for (i = 0; i < len-1 ; i++) {
for (j = len-1; j > i; j--) {
if (elem[j-1]>elem[j]){
swap(elem[j-1],elem[j]);
flog= true;
}
}
// 如果在一趟比较中没有发生任何交换,说明有序,可以提前结束排序
if (flog== false){
return;
}
}
}
2、快速排序
递归实现
核心原理
核心就是分治思想
例如:28 7 98 28 60 83 34 59 69 14
1、取第一个元素 28,
2、把比28小的元素移到28的左侧,把比28的元素移到28的右侧
即形成7 14 28 [28] 98 60 83 34 59 69
3、把左侧和右侧再次使用快排进行排序,左侧去7,右侧去98
即形成[7] 14 28 [28] 60 83 34 59 69 [98],循环进行直至排序完成
代码实现
步骤 挖坑法
(1) 初始:64 7 98 28 60 83 34 59 69 14
(2) 用临时变量 pivot=64,high = 14 < 64,用high的值把low覆盖(此时low不动,移动high)
第一次: 14 7 98 28 60 83 34 59 69 14
(3) low++ 当找到 low=98 > 64,用low的值把high覆盖 (此时high不动,移动low)
第二次: 14 7 98 28 60 83 34 59 69 98
---循环往复---
(4) 当low和high相等时,循环结束,此时low为NULL,可以把64覆盖回来
==14 7 59 28 60 34 [64] 83 69 98==
(5) 返回
// 分割函数
int partition(Elemtype *elem, int low, int high){
//最左边的作为分割值并存储;
Elemtype piovt=elem[low];
while (low<high){
while (low<high&&elem[high]>=piovt) //从后往前遍历,找到比分割值小的
high--;
elem[low]=elem[high]; // 把比分割值小的元素放到low
while (low<high&&elem[low]<=piovt) //从前往后遍历,找到比分割值大的
low++;
elem[high]=elem[low]; // 把比分割值大的元素放到high
}
elem[low]=piovt;
// 返回下标
return low;
}
// 快速排序
void QuickSort(Elemtype *elem, int low, int high){
if (low<high){
// low取最左侧元素,high取最右侧元素
// pivot 用来存分隔值位置
int pivot_pos=partition(elem, low, high);
// 前一半递归拍好
QuickSort(elem, low, pivot_pos-1);
// 后一半递归拍好
QuickSort(elem, pivot_pos+1, high);
}
}
二、插入类排序
插入排序分为:直接插入排序,折半插入排序,希尔插入排序
直接插入排序
原理
如果序列只有一个数,那么该序列自然有序,把后面的数当做依次要插入的序列。
通过外出循环控制要插入的数。
用一个变量存要插入的值,比较arr[0]和arr[1],如果arr[0]>arr[i]发生移动,反之不移动。
代码实现
// 插入排序
void InsertSort(Elemtype *elem, int n){
int i,j,insertVal;
for (i = 1; i < n; i++) { //外出循环控制要插入的数
insertVal=elem[i]; //用一个变量存要插入的值
for (j = i-1; j>=0 && elem[j]>insertVal; j--) {
elem[j+1]=elem[j];
}
elem[j+1]=insertVal; //把要插入的元素放入对应的位置
}
}
三、选择类排序
1、简单选择排序
原理解析
假设排序表L[1...n]
,第i
趟排序即从L[i,n]
选出最小的元素与L[i]
交互,每一趟排序确定一个元素的最终位置,经过n-1
次后完成排序
步骤
1. 假设第一个元素最小,把第一个元素的下标赋给min
2. 内层循环比较 1- 9 号元素,谁更小就把它的下标赋值给min,
3. 一轮比较结束后,把第一个元素和最小元素交换
最初 | 3 87 2 93 78 56 61 38 12 40 |
---|---|
第一轮比较后标记 | 3 87 2 93 78 56 61 38 12 40 |
第一次交互 | 2 87 3 93 78 56 61 38 12 40 |
第二轮比较后标记 | 2 87 3 93 78 56 61 38 12 40 |
第二次交互 | 2 3 87 93 78 56 61 38 12 40 |
… | … |
代码实现
// 交换函数
void swap(int &a, int &b){
int temp=a;
a=b;
b=temp;
}
// 选择排序
void selectionSort(Elemtype *elem, int n){
int min;
for (int i = 0; i < n; i++) {
min=i;
// j从第一个元素开始
for (int j = i+1; j < n; j++) {
if (elem[j]<elem[min]){
min=j; // 标记最小元素
}
}
// 循环比较了一次后, 交换位置
if (min!=i){
swap(elem[i],elem[min]);
}
}
}
2、堆排序
原理解析
堆一种特殊的数状结构。
特点
1.给定堆中任意结点P和C,若P是C的父节点,则P的值小于等于(或大于等于)C的值
2.若父节点的值恒小于等于子节点,称之为最小堆,反之称之为最大堆
3.堆中最顶结点称之为根节点
4.根节点没有父节点
假设有 3 87 2 93 78 56 61 38 12 40 十个元素,使用层次建树法建立成完全二叉树_相关内容(六)数据结构–二叉树_
能将二叉树中每个元素对应到数组下标的数据结构叫做堆。
最后一个父亲结点下标 N/2-1
(完全二叉树的特性)
代码实现
步骤(包含两个大步骤)
1.实现大根堆
1.1 从叶子结点开始比较,
如果父节点只有一个子节点,并且子节点的值比父节点的值大,子节点和父节点交换位置,反之不用交换位置;
如果父节点有两个子节点,先选出较大的子节点与父节点进行比较,子节点的值比父节点的值大,子节点和父节点交换位置,直至循环到arr[0]
再由arr[1]作为根节点和子节点进行比较,循环往复直至实现大根堆
2.实现排序
先把顶部元素与最后一个元素进行交换,
再次通过大根堆将处理最后元素的最大元素移动到最顶部
再次把顶部元素与最后一个今夕交换,
再次通过大根堆调整元素,循环往复直至排序结束
需要公式:
最后一个父亲结点=n/2-1;
左子树下标=2*dad+1;
右子树下标=左子树下标+1;
// 把某个子树调整为大根堆
void AdjustDown(Elemtype *elem, int k, int len){
int dad=k; // 父亲下标
int son=2*dad+1; // 左孩子下标
while (son<len){
// 最后的右孩子下标小于总长度
if (son+1<len && elem[son]<elem[son+1]){
son++; // 那右孩子和父亲比
}
// 拿孩子和父亲比较
if (elem[son]>elem[dad]){
swap(elem[son],elem[dad]);
// 当父子结点交换后,如果交换成子节点的值同时也是父节点
// son重新作为dad去验证;
dad=son;
son=2*dad+1;
} else {
break;
}
}
}
// 堆排序
void heap_Sort(Elemtype *elem, int len){
// 实现大根堆
for (int i = len/2-1; i >=0 ; i--) {
AdjustDown(elem, i, len);
}
// 交换元素排序
swap(elem[0], elem[len-1]);
for (int i = len-1; i>1; i--) { // i代表的是剩余无序数的数组的长度
AdjustDown(elem ,0, i); // 调整剩余元素变成大根堆
swap(elem[0],elem[i-1]);
}
}
四、归并排序
原理
两两归并,将每两个元素归为一组,在组内排序,再把两个有序组合并成一个有序小组,不断进行最终合并成一个有序组。
49 38 65 97 76 13 27 初始化
------------------------------------------------------------
[38 49] [65 97] [13 76] 27 第一次归并
------------------------------------------------------------
[38 49 65 97] [13 76 27] 第二次归并
------------------------------------------------------------
[13 27 38 49 65 76 97] 第三次归并
代码实现
递归实现
// 合并两个有序数组
void Merge(Elemtype *elem, int low, int mid, int high){
static Elemtype B[N]; //static的目的是无论递归调用了多少次,都只有一个B[N];
int i, j, k;
// 复制数组
for (i = low; i <=high ; i++) {
B[i]=elem[i];
}
k=low;
// 合并两个有序数组
for (i = low, j=mid+1; i <= mid && j<=high;) {
if (B[i]<B[j]){
elem[k]=B[i];
k++;
i++;
} else {
elem[k]=B[j];
k++;
j++;
}
}
// 把某一个循环数组剩余的元素放进来
while (i<=mid){
elem[k]=B[i];
i++;
k++;
}
while (j<=high){
elem[k]=B[j];
j++;
k++;
}
}
// 归并排序
void MergeSort(Elemtype *elem, int low, int high){
if (low<high){
int mid=(low+high)/2;
MergeSort(elem,low,mid); // 排序好前一半
MergeSort(elem,mid+1,high); // 排序好后一半
Merge(elem,low,mid, high); // 合并数组
}
}