目录
一、交换类排序
1.冒泡排序
演示地址:
注意:n个数
1.交换条件 如果从小到大排列那么 判断 大数在前就发生交换
2.注意外层循环控制趟数 i从0 到 n-1趟 i++
3.注意内层循环控制遍历着进行交换 从后往前 n-1 到 i j不能取0因为里面有j-1 j--
4.注意flag是看是否进行交换 经过一趟后没有发生交换则序列本身有序
#include <stdio.h>
void BubbleSort(int A[10],int n);
void swap(int &a,int &b){
int temp=a;
a=b;
b=temp;
}
int main() {
int A[10]={9,7,8,6,5,4,3,2,1,0};
BubbleSort(A,10);
for (int i = 0; i < 10; ++i) {
printf("%2d",A[i]);
}
return 0;
}
void BubbleSort(int A[10],int n) {
//控制趟数
for (int i = 0; i < n - 1; ++i) {
//交换标志
bool flag= false;
//控制交换
for (int j = n-1; j > i; j--) {
//从小到大排列 大的在前那么就要发生交换了
if (A[j-1]>A[j]){
swap(A[j],A[j-1]);
flag= true;
}
}
if (!flag){
//如果经过依次检查后没有发生交换则该数组本身有序
return;
}
}
}
2.快速排序
注意:分治思想
挖坑法(不断覆盖)得到分隔值的位置
注意每次循环都要判断 low<high
注意出现相等的数我们不用管--不需要处理
最终low与high是相等的
演示地址:
16.5 快速排序
64, 94, 95, 79, 69, 84, 18, 22, 12 ,78
把比64小的放在64的左边,把比64大的放在它的右边
18, 22, 12 64 ,94, 95, 79, 69, 84,7816.6 快速排序实战
原来的
64, 94, 95, 79, 69, 84, 18, 22, 12 ,78
pivot=64
12, 22, 18, 64, 69, 84, 79, 95, 94 ,78
high
low
pivot=64
12, 94, 95, 79, 69, 84, 18, 22, 12 ,78
high
low
#include <stdio.h>
//得到分割值的位置--挖坑法(不断覆盖)
int getPosition(int A[],int low,int high){
//把low的位置作为分隔值
int provt=A[low];
while (low<high){
//从high往前找比分隔值小的数的位置--注意循环里面也要判断low<high
while (low<high&&A[high]>=provt){
//没有找到就往前移动
high--;
}
//出循环 这时A[high]<provt 就找到了 这个小的元素
//我们需要把它覆盖到low的位置上
A[low]=A[high];
//从low往后找 直到找到比分隔值大的元素--注意循环里面也要判断low<high
while (low<high&&A[low]<=provt){
low++;
}
//此时A[low]>provt 找到了大的元素我们需要把它放到分割值的右边
//因此我们需要把它覆盖到high的位置上
A[high]=A[low];
}
//不要忘记把分隔值赋值上去
A[low]=provt;
//此时low=high的
return low;
}
//快速排序--分治思想
void quickSort(int A[],int low,int high){
if (low>=high){
return;
}
//进行第一次快速排序得到分隔值的位置
int position=getPosition(A,low,high);
//接下来排列分隔值的左边
quickSort(A,low,position-1);
//接下来排列分隔值的右边
quickSort(A,position+1,high);
}
二、 插入排序
1.直接插入排序
注意:
外层循环控制:无序序列的个数--即要插入的元素个数 i从 1到len-1
内层循环控制交换 注意条件
此时j的位置是从i-1开始 j>=0&&A[j]>inserVal 那么就将后面的元素进行覆盖
注意最后要将j+1的位置即为要插入的位置
因为循环完成后我们直到j这个位置是小于inserVal的所以要插入到这个位置的后面
16.7 插入排序的原理及实战
insertVal=1
i
1 2 3 72 78 87 61 38 12 40
j
//玩坑法(覆盖) 插入排序
void insertSort(int A[],int len){
int i,j,inserVal;
//注意循环起始 此时有序数列中左半边为1个元素 所以无序的右半边从 1开始到len-1
for (i = 1; i < len; ++i) {//外层循环控制要插入的数量
//存储当前要插入的值
inserVal=A[i];
//那么我们判断就从有序的开始 i-1 到0--并且这个元素大于要插入的元素那么就进行覆盖
for (j = i-1; j >=0&&A[j]>inserVal ; j--) {
//就将后面的元素覆盖成大的那一个
A[j+1]=A[j];
}
//最终要将插入元素进行插入-这里要使用到j因此要将j定义到循环外面
//如果最终j--到0了 那么此时将j+1的位置也就是0的位置即插入位置
A[j+1]=inserVal;
}
}
2.折半插入排序
3.希尔排序
三、选择排序
1.简单选择排序
注意:
内层循环:
找到那么就记录该位置--必须写在这 因为我们要把后续的位置找全 找出最小 如果写在循环条件处那么 它就会找到一个就交换一次 而不是最小才交换 注意最小条件
//简单选择排序
void selectSort(int A[],int len){
int min;
for (int i = 0; i < len - 1; ++i) {
//外层循环控制趟数
//每次都找出一个最小的 交换那么 只需要找len-1次
//记录最小值位置
min=i;
//内层循环从未确定的序列中找最小的位置 因此从i+1开始到结束
for (int j = i+1; j < len; ++j) {
//找到那么就记录该位置--必须写在这 因为我们要把后续的位置找全 找出最小
//如果写在循环条件处那么 它就会找到一个就交换一次 而不是最小才交换 注意最小条件
if (A[j]<A[min]){
min=j;
}
}
//进行交换
swap(A[min],A[i]);
}
}
2.堆排序
每次调整大根堆的时间复杂度为树的高度 调整n次 因此为n*logn
注意:
1.循环调整子树为大根堆的循环条件(son<len)--注意调整完后要重置dad与son
因为这颗子树可能下面还有子树
我们需要让dad=son的 son=2*dad+1 这这颗子树也满足大根堆依次往复
2.注意len是代表什么
3.注意建立大根堆的循环条件 i是从最后一个父节点开始到0
4.注意循环剩余元素为大根堆的循环条件
i代表无序的个数 i从len-1到1 直到调整到剩余俩个元素
//交换
void swap(int &a,int &b){
int temp=a;
a=b;
b=temp;
}
/**
* 调整一个子树为大根堆
* @param A
* @param dad 最后一个节点的父节点位置
* @param len 需要调整的长度
*/
void adjustMaxTree(int A[],int last,int len){
int dad=last;
int son=2*dad+1;
while (son<len){
if (son+1<len&&A[son]<A[son+1]){
//比较左 右 孩子中最大的元素
//如果左孩子 小于 右孩子--最大元素为右孩子
son++;//拿右孩子
}
//得到最大元素为A[son]
if (A[son]>A[dad]){//如果孩子大于父亲 交换
//将孩子中最大的放到根节点上
swap(A[son],A[dad]);
//重置父子节点位置
dad=son;//son重新作为dad去验证下面的子树是否为大根堆--
son=2*dad+1;
//继续循环重新进行调整我们交换完的下面的那颗子树是否满足大根堆
} else{
//这个子树中的所有的子树全部满足大根堆
break;
}
}
}
//堆排序
void heapSort(int A[],int len){
//建立大根堆
for (int i = len/2-1; i >= 0; i--) {
//从最后一个子树的父亲节点开始到0
adjustMaxTree(A,i,len);
}
//将最大元素放到数组末尾
swap(A[0],A[len-1]);
//调整剩余元素为大根堆并从小到大排列--调整到只剩2个元素
for (int i = len-1; i > 1; i--) {
//i代表的是无序的个数
//因为把最大的元素放到了最后一个树节点上 那么每次都要调整 父亲节点地址为0 长度为 i的元素为大跟堆
adjustMaxTree(A,0,i);
//调整之后进行交换--交换根部元素
swap(A[0],A[i-1]);
}
}
完整代码
#include <stdio.h>
//交换
void swap(int &a,int &b){
int temp=a;
a=b;
b=temp;
}
/**
* 调整一个子树为大根堆
* @param A
* @param dad 最后一个节点的父节点位置
* @param len 需要调整的长度
*/
void adjustMaxTree(int A[],int last,int len){
int dad=last;
int son=2*dad+1;
while (son<len){
if (son+1<len&&A[son]<A[son+1]){
//比较左 右 孩子中最大的元素
//如果左孩子 小于 右孩子--最大元素为右孩子
son++;//拿右孩子
}
//得到最大元素为A[son]
if (A[son]>A[dad]){//如果孩子大于父亲 交换
//将孩子中最大的放到根节点上
swap(A[son],A[dad]);
//重置父子节点位置
dad=son;//son重新作为dad去验证下面的子树是否为大根堆--
son=2*dad+1;
//继续循环重新进行调整我们交换完的下面的那颗子树是否满足大根堆
} else{
//这个子树中的所有的子树全部满足大根堆
break;
}
}
}
//堆排序
void heapSort(int A[],int len){
//建立大根堆
for (int i = len/2-1; i >= 0; i--) {
//从最后一个子树的父亲节点开始到0
adjustMaxTree(A,i,len);
}
//将最大元素放到数组末尾
swap(A[0],A[len-1]);
//调整剩余元素为大根堆并从小到大排列--调整到只剩2个元素
for (int i = len-1; i > 1; i--) {
//i代表的是无序的个数
//因为把最大的元素放到了最后一个树节点上 那么每次都要调整 父亲节点地址为0 长度为 i的元素为大跟堆
adjustMaxTree(A,0,i);
//调整之后进行交换--交换根部元素
swap(A[0],A[i-1]);
}
}
int main() {
int A[10]={58,42,13,58,44,53,73,75,2,69};
int len=10;
heapSort(A,len);
for (int i = 0; i < 10; ++i) {
printf("%3d",A[i]);
}
return 0;
}
四、归并排序
注意:
1.复制临时数组 static int B[N];
2.复制的时候for循环从low开始 避免覆盖掉之前的元素
3.合并的时候 注意合并的那个数组A k是从low开始 避免覆盖掉之间的元素
4.不要忘记 剩余元素的合并
#define N 50
void merge(int A[],int low,int mid,int high){
//注意static无论递归多少次都只有一个B
static int B[N];
//复制临时数组--注意这里是low 到high
for (int i = low; i <= high; ++i) {
B[i]=A[i];
}
int i,j,k;
//注意k是从low开始
for ( i = low,j=mid+1,k=low;i<=mid&&j<=high; k++) {
//i j 的元素值进行比较 小的放入A中--并且放入的元素的那个向后移动
if (B[i]<B[j]){
A[k]=B[i++];
} else{
A[k]=B[j++];
}
}
//看哪个有剩余都放入到A中即可
while (i<=mid){
A[k++]=B[i++];
}
while (j<=high){
A[k++]=B[j++];
}
}
void mergeSort(int A[],int low,int high){
if (low<high){
int mid=(low+high)/2;
//排序前半边
mergeSort(A,low,mid);
//排序号后半边
mergeSort(A,mid+1,high);
//合并为有序数组
merge(A,low,mid,high);
}
}
完整代码
#include <stdio.h>
#define N 50
void merge(int A[],int low,int mid,int high){
//注意static无论递归多少次都只有一个B
static int B[N];
//复制临时数组--注意这里是low 到high
for (int i = low; i <= high; ++i) {
B[i]=A[i];
}
int i,j,k;
//注意k是从low开始
for ( i = low,j=mid+1,k=low;i<=mid&&j<=high; k++) {
//i j 的元素值进行比较 小的放入A中--并且放入的元素的那个向后移动
if (B[i]<B[j]){
A[k]=B[i++];
} else{
A[k]=B[j++];
}
}
//看哪个有剩余都放入到A中即可
while (i<=mid){
A[k++]=B[i++];
}
while (j<=high){
A[k++]=B[j++];
}
}
void mergeSort(int A[],int low,int high){
if (low<high){
int mid=(low+high)/2;
//排序前半边
mergeSort(A,low,mid);
//排序号后半边
mergeSort(A,mid+1,high);
//合并为有序数组
merge(A,low,mid,high);
}
}
int main() {
int A[10]={58,42,13,58,44,53,73,75,2,69};
int low=0,high=9;
mergeSort(A,low,high);
for (int i = 0; i <10; ++i) {
printf("%3d",A[i]);
}
return 0;
}
五、汇总
六、OJ练习
注意:
1.冒泡排序:正确表示前一个数和后一个数
j--
#include <stdio.h>
//读取10个整型数据12 63 58 95 41 35 65 0 38 44,
// 然后通过冒泡排序,快速排序,插入排序,分别对该组数据进行排序,输出3次有序结果,每个数的输出占3个空格
//交换
void swap(int &a,int &b){
int temp=a;
a=b;
b=temp;
}
void print_A(int A[],int len){
//排序完成
for (int i = 0; i < len; ++i) {
printf("%3d",A[i]);
}
printf("\n");
}
//冒泡排序
void BubbleSort(int A[],int len){
bool flag= false;
for (int i = 0; i < len ; ++i) {
for (int j = len-1; j > i; j--) {
//从小到大 如果前面比后面大交换
if (A[j-1]>A[j]){
swap(A[j-1],A[j]);
flag= true;
}
}
if (!flag){
return;
}
}
print_A(A,len);
}
//挖坑法--分割
int patition(int A[],int low,int high){
//先将low的位置作为分割值
int provt=A[low];
while (low<high){
//从后往前找比分隔值小的数进行覆盖
while (low<high&&A[high]>=provt){
high--;
}
//此时A[high]<provt
A[low]=A[high];
//从前往后找比分隔值大数进行覆盖
while (low<high&&A[low]<=provt){
low++;
}
//此时A[low]>provt
A[high]=A[low];
}
//填入分隔值
A[low]=provt;
return low;
}
//快速排序--分治
void quickSort(int A[],int low,int high){
if (low>high){
return;
}
//确定一个分割位置
int position=patition(A,low,high);
//排序左边
quickSort(A,low,position-1);
//排序右边
quickSort(A,position+1,high);
}
//插入排序
void inserSort(int A[],int len){
int i,j,insertVal;
for (i = 1; i < len; ++i) {
insertVal=A[i];
for (j = i-1; j >=0&&A[j]>insertVal; j--) {
//交换
A[j+1]=A[j];
}
//插入
A[j+1]=insertVal;
}
print_A(A,len);
}
int main() {
int A[10];
for (int i = 0; i < 10; ++i) {
int x;
scanf("%d",&x);
A[i]=x;
}
int len=10;
BubbleSort(A,len);
quickSort(A,0,len-1);
print_A(A,len);
inserSort(A,len);
return 0;
}
2.选择、堆、归并
选择排序:注意 找到最小的后是交换 而不是覆盖
堆排序: 注意第一次建立完大根堆后 将根节点(最大)与最后一个节点进行交换
然后再依次进行 大根堆的建立 建立的时候 因为最后都改变了根节点与最后一个元 素的值 那么每次建立大根堆的父节点都是从0开始 注意起始条件
归并排序:注意不要忘记剩余节点也要合为一个数组
//交换
void swap(int &a,int &b){
int temp=a;
a=b;
b=temp;
}
//选择排序--找最小的放到前面/找最大的放到后面
void SelectSort(int A[],int len){
int i=0,min,j;
int temp=A[i];
while (i<len){
min=i;
for (j = i+1; j < len; ++j) {
if (A[min]>A[j]){
min=j;
}
}
//找到了最小值
swap(A[i++],A[min]);
}
}
//建立大根堆
void buildMaxTree(int A[],int last,int len){
int dad=last;
int son=last*2+1;
while (son<len){
if (son+1<len&&A[son]<A[son+1]){
son++;//拿到右孩子作为最大
}
//最大的孩子节点值与父节点比较替换
if (A[son]>A[dad]){
swap(A[son],A[dad]);
//重置dad与son 为下一个子树
dad=son;
son=2*dad+1;
} else{
//全部变为大根堆 没有任何交换后--退出循环
break;
}
}
}
//堆排序
void heapSort(int A[],int len){
//对于初始数组建立大根堆
for (int i = len/2-1; i >= 0; i--) {
buildMaxTree(A,i,len);
}
//之后交换根节点与最后一个节点
swap(A[0],A[len-1]);
//堆剩余元素进行大根堆的依次建立--直到仅剩余2个节点
for (int i = len-1; i >1 ; i--) {//i为当今剩余的无序节点数量
//因为交换了 那么改变的就是A[0]节点开始 的大根堆--因此此时父节点为0
buildMaxTree(A,0,i);
//交换改变
swap(A[0],A[i-1]);
//继续上述操作
}
}
//归并排序--合并有序数组
void merge(int A[],int low,int mid,int high){
static int B[N];
for (int i = low; i <= high; ++i) {
B[i]=A[i];
}
int i,j,k;
for (i = low,j=mid+1,k=low;i<=mid&&j<=high; k++) {
if (B[i]<B[j]){
A[k]=B[i++];
} else{
A[k]=B[j++];
}
}
//不要忘记还有剩余元素
while (i<=mid){
A[k++]=B[i++];
}
while (j<=high){
A[k++]=B[j++];
}
}
//归并排序
void mergeSort(int A[],int low,int high){
if (low>=high){
return;
}
int mid=(low+high)/2;
//归并前半部分
mergeSort(A,low,mid);
//归并后半部分
mergeSort(A,mid+1,high);
//合并为有序数列
merge(A,low, mid,high);
}
void printSort(int A[],int len){
for (int i = 0; i < len; ++i) {
printf("%3d",A[i]);
}
}
完整代码
#include <stdio.h>
# define N 50
/**
* 读取10个整型数据12 63 58 95 41 35 65 0 38 44,
* 然后通过选择排序,堆排序,分别对该组数据进行排序,输出2次有序结果,每个数的输出占3个空格
*/
/**
* 读取10个整型数据12 63 58 95 41 35 65 0 38 44,
* 然后通过归并排序,对该组数据进行排序,输出有序结果,每个数的输出占3个空格
*/
//交换
void swap(int &a,int &b){
int temp=a;
a=b;
b=temp;
}
//选择排序--找最小的放到前面/找最大的放到后面
void SelectSort(int A[],int len){
int i=0,min,j;
int temp=A[i];
while (i<len){
min=i;
for (j = i+1; j < len; ++j) {
if (A[min]>A[j]){
min=j;
}
}
//找到了最小值
swap(A[i++],A[min]);
}
}
//建立大根堆
void buildMaxTree(int A[],int last,int len){
int dad=last;
int son=last*2+1;
while (son<len){
if (son+1<len&&A[son]<A[son+1]){
son++;//拿到右孩子作为最大
}
//最大的孩子节点值与父节点比较替换
if (A[son]>A[dad]){
swap(A[son],A[dad]);
//重置dad与son 为下一个子树
dad=son;
son=2*dad+1;
} else{
//全部变为大根堆 没有任何交换后--退出循环
break;
}
}
}
//堆排序
void heapSort(int A[],int len){
//对于初始数组建立大根堆
for (int i = len/2-1; i >= 0; i--) {
buildMaxTree(A,i,len);
}
//之后交换根节点与最后一个节点
swap(A[0],A[len-1]);
//堆剩余元素进行大根堆的依次建立--直到仅剩余2个节点
for (int i = len-1; i >1 ; i--) {//i为当今剩余的无序节点数量
//因为交换了 那么改变的就是A[0]节点开始 的大根堆--因此此时父节点为0
buildMaxTree(A,0,i);
//交换改变
swap(A[0],A[i-1]);
//继续上述操作
}
}
//归并排序--合并有序数组
void merge(int A[],int low,int mid,int high){
static int B[N];
for (int i = low; i <= high; ++i) {
B[i]=A[i];
}
int i,j,k;
for (i = low,j=mid+1,k=low;i<=mid&&j<=high; k++) {
if (B[i]<B[j]){
A[k]=B[i++];
} else{
A[k]=B[j++];
}
}
//不要忘记还有剩余元素
while (i<=mid){
A[k++]=B[i++];
}
while (j<=high){
A[k++]=B[j++];
}
}
//归并排序
void mergeSort(int A[],int low,int high){
if (low>=high){
return;
}
int mid=(low+high)/2;
//归并前半部分
mergeSort(A,low,mid);
//归并后半部分
mergeSort(A,mid+1,high);
//合并为有序数列
merge(A,low, mid,high);
}
void printSort(int A[],int len){
for (int i = 0; i < len; ++i) {
printf("%3d",A[i]);
}
}
int main() {
int A[10],x;
for (int i = 0; i < 10; ++i) {
scanf("%d",&x);
A[i]=x;
}
mergeSort(A,0,9);
printSort(A,10);
return 0;
}
七、真题演练
注意:
1.快排算法注意枢纽选择 与覆盖替换顺序 最后不要忘记填充回去
2.理解low mid high low0 high0的作用
//基于快排的思想
int setPartition(int A[],int len){
int low=0,high=len-1,mid=len/2,low0=0,high0=high,pivotKey;
bool flag= true;
while (flag){
//快排
pivotKey=A[low];//枢纽选择--注意在循环外
while (low<high){
//从后往前找小于枢纽的
while (low<high&&A[high]>=pivotKey) {
high--;
}
//找到一个小于的数覆盖
A[low]=A[high];//--画图注意不要弄反了
//从前往后找大于枢纽的
while (low<high&&A[low]<=pivotKey){
low++;
}
//找到一个大于的数覆盖
A[high]=A[low];//--画图注意不要弄反了
}
//不要忘记最后循环结束后要填充pivotKey
A[low]=pivotKey;
//经过依次分割后 如果low所处的位置正好是中心分割位置那么就退出 找到了分割的俩个数组
if (low==mid-1){
flag= false;
}
//经过依次分割后 low在中心分割位的左边
if (low<mid-1){
//重置low与high--low0来暂存下一次的起始位置 high0来暂存下一次的终点位置
//low向后移动 high不变因此需要用到high0--画图体会
low0=++low;
high=high0;
}
//经过依次分割后 low在中心分割位的右边
if (low>mid-1){
//重置low与high--low0来暂存下一次的起始位置 high0来暂存下一次的终点位置
//high向前移动 low不变因此需要用到low0--画图体会
low=low0;
high0=--high;
}
}
//划分完成
//和 s1 s2
int s1=0,s2=0;
for (int i = 0; i < mid; ++i) {
s1+=A[i];
}
for (int i = mid; i < len; ++i) {
s2+=A[i];
}
//这就刚好 s2和最大 s1和最大
return s2-s1;
}
完整代码
#include <stdio.h>
//基于快排的思想
int setPartition(int A[],int len){
int low=0,high=len-1,mid=len/2,low0=0,high0=high,pivotKey;
bool flag= true;
while (flag){
//快排
pivotKey=A[low];//枢纽选择--注意在循环外
while (low<high){
//从后往前找小于枢纽的
while (low<high&&A[high]>=pivotKey) {
high--;
}
//找到一个小于的数覆盖
A[low]=A[high];//--画图注意不要弄反了
//从前往后找大于枢纽的
while (low<high&&A[low]<=pivotKey){
low++;
}
//找到一个大于的数覆盖
A[high]=A[low];//--画图注意不要弄反了
}
//不要忘记最后循环结束后要填充pivotKey
A[low]=pivotKey;
//经过依次分割后 如果low所处的位置正好是中心分割位置那么就退出 找到了分割的俩个数组
if (low==mid-1){
flag= false;
}
//经过依次分割后 low在中心分割位的左边
if (low<mid-1){
//重置low与high--low0来暂存下一次的起始位置 high0来暂存下一次的终点位置
//low向后移动 high不变因此需要用到high0--画图体会
low0=++low;
high=high0;
}
//经过依次分割后 low在中心分割位的右边
if (low>mid-1){
//重置low与high--low0来暂存下一次的起始位置 high0来暂存下一次的终点位置
//high向前移动 low不变因此需要用到low0--画图体会
low=low0;
high0=--high;
}
}
//划分完成
//和 s1 s2
int s1=0,s2=0;
for (int i = 0; i < mid; ++i) {
s1+=A[i];
}
for (int i = mid; i < len; ++i) {
s2+=A[i];
}
//这就刚好 s2和最大 s1和最大
return s2-s1;
}
int main() {
int A[10]={4,1,12,18, 7,13,18,16,2,15};
int diffence;
diffence=setPartition(A,10);
printf("%d",diffence);
return 0;
}