内部排序:
一、插入排序
**壹.直接插入排序**
自己的理解:
插入排序是将数组当成摸牌
1.默认的将第一张牌a[0]认为已经在手上了,从i=1开始摸牌,暂时放在temp里,即temp=a[i]
2.j=i表示当前放在temp里的牌应该放的位置,即a[j],为了确定是哪一张牌应该放在这个位置,需要用temp与a[j-1](当前应该放的位置前一个位置)比较,符合排序条件就放谁。
3.排序条件:
升序:如果摸上来的牌比手上某一个牌要小,将该牌向后挪一个位置,将摸上来的牌插入空缺
降序: 如果摸上来的牌比手上某一个牌要大,将该牌向后挪一个位置,将摸上来的牌插入空缺
如果不满足条件,就将摸上来的牌放在末位
#include<cstdio>
void insertsort(int *a1,int n);
int main(void)
{
int a[10] = {12,34,5,689,-43,56,-21,0,24,65};
int *p = a;
insertsort(p,10);
for(p;p<a+10;p++)
{
printf("%d ",*p);
}
return 0;
}
void insertsort(int *p,int n){
for(int i=1;i<n;i++){
int temp = p[i];
int j;
for(j = i;j>0 && temp > p[j-1];j--)//降序
p[j] = p[j-1];
p[j] = temp;
}
}
输出:
升序:-43 -21 0 5 12 24 34 56 65 689
降序:689 65 56 34 24 12 5 0 -21 -43
注意事项:
在insertsort函数中,内层for循环,判断条件
1.不能把a[j]>temp,移至循环内部写成if语句,否则会出错
2.不能把所有的temp改写成a1【i】,因为是在同一个数组中进行操作,若果出现后移一位的错作的时候,a【i】所带表的值是变化的,所以需要一个中间变量temp来保存移位之前a【i】所表示的值。
**贰.希尔排序**
希尔排序是:将大数组按照每间隔d个单位的元素组成一个小数组,再在小组内进行插入排序,让小数组在大数组中实现间隔d个单位仍然是有序的。不断缩小,间隔d来达到大数组全部有序的目的。
具体思路:
1.将大数组按照元素每间隔d=n/2构成一个小数组
2.各小数组内,进行插入排序,与一般插入排序不同的是,小数组内各元素的下标对应关系(以第一个,二个分组为例,d=5)是:第一组(0,5,10,…)、第二组(1,6,11)。所以在各个小组内第一次摸上来的牌,下标应该是5,6,7等等,即i=d,i=d+1等
3.为了方便描述各个小组之间的元素,都采用以第一个分组第一次摸上来的牌的下标i为基准,用i自增来表示。
代码为:
//注释都是在插入排序的基础上备注的
void shell_sort(int*p,int n){
for(int d=n/2;d>0;d/=2){//d为间隔
for(int i=d;i<n;i++){//i为小组内第一次摸上来的牌的下标,并且以第一个小组为基准。
int temp = p[i];
int j;
for(j=i;j>=d && temp >p[j-d];j-=d)//j仍然表示当前摸的牌应该放的位置
p[j] = p[j-d];
p[j] = temp;
}
}
}
```cpp
在这里插入代码片
二、交换排序
**壹、冒泡排序**
自己的理解:
对数组进行i=n-1趟排序,每趟排序排出一个最大或者最小值出来,最后一个元素不用排序,所以是n-1趟,每结束依次就i–。
每一趟的操作:对下标在[0,i]之间的元素,进行排序
void bubblesort(int *p,int n){
for(int i=n-1;i>=0;i--){
int flag=0;//看是否发生交换
for(int j=0;j<i;j++){
if(p[j] > p[j+1])//升序排列
swap(p[j],p[j+1]);
flag = 1;//发生交换,将标识置为1
}
if(flag == 0)
break;//没有发生交换就说明已经完成了排序
}
}
**贰、快速排序**
思路:
1.设立一个基准元素a,使左边所有元素不超过a;右边所有元素不小于a。
2.递归的将整个数组划分成若干跟小区间,重复进行1.
3.小区间内实现1是依靠两个指针left,right,不断向中间移动,当right所指元素小于a,就将其放置在left所指位置;left所指元素大于a时就将其放在right所指位置。
4.由于在数组元素排列较为有序的时候,快速排序的效率不高,所以生成一个随机位置,作为基准元素TEMP,以打乱数组的有序性,提高效率。
5.同样用分割函数和合并函数,来完成。
//合并函数
int partition(int A[],int left,int right){
int p = round(1.0*rand()/RAND_MAX*(right-left)+left);//在范围[left,right]内生成一个数,作为基准元素的下标
swap(A[p],A[left]);//交换位置,打乱顺序
int temp = A[left];
while(left<right){
while(left<right && A[right] > temp)
right--; //当right指针所指元素小于temp时,持续向左移动
A[left] = A[right]; //right所指元素大于temp就将其放在left所指位置
while(left<right && A[left] <= temp)
left++; //当left指针所指元素小于temp时,持续向右移动
A[right] = A[left]; //left所指元素大于temp就将其放在right所指位置
}
A[left] = temp; //基准元素放在中间。
return left;
}
//分割函数
void quicksort(int A[],int left,int right){
if(left<right){
int pos = partition(A,left,right);//返回基准元素的下标
quicksort(A,left,pos-1);
quicksort(A,pos+1,right);
}
合并写法:
void quicksort(int a[],int left,int right){
int i =left,j=right;
if(left<right){
int p = round(1.0*rand()/RAND_MAX*(right-left)+left);
swap(a[p],a[left]);
int temp = a[left];
while(i<j){
while(i<j && a[j] > temp)
j--;
a[i] = a[j];
while(i<j && a[i] <= temp)
i++;
a[j] = a[i];
}
a[i] = temp;
quicksort(a,left,i-1);
quicksort(a,i+1,right);
}
}
ps:1.合并函数内:对右侧的处理应该先做,因为将基准元素赋值给了temp,等同于空出了该位置,将右侧小于基准元素的值放过来不会对数组产生影响,但是如果先操作左侧就会出现覆盖掉右侧元素的情况。
2.生成随机数语句:int p = round(1.0rand()/RAND_MAX(right-left)+left);
需要包含的头文件:time.h,stdlib.h。
并且需要加上:srand((unsigned)time(NULL))
3.rand()函数生成的随机数是介于【0,RAND_MAX】之间的。
想要得到[a,b]范围内的随机数,方法为rand()%(b-a+1)+a。
如果需要生成区间的数值太大,就先生成一个[0,1]范围内的浮点数,然后乘以这个区间加上下限即可。
也就是:round(1.0rand()/RAND_MAX(right-left)+left);
三、选择排序
**壹、简单选择排序**
选择:自己的理解:
简单选择排序就是进行n-1趟操作,操作的次数用i表示,每趟操作都选出一个最小值或者最大值。选出来之后将最大值或者最小值与第i个元素进行交换。
void Selectsort(int *arr,int n)
{
for(int i=0;i<n-1;i++)//i确定排序趟数
{
int k = i; //k保存当前最小或者最大值的下标
for(int j=i+1;j<n;j++) //遍历数组,比较元素大小
{
if(arr[j]<arr[k]) //这样写是升序,降序是arr[j]>arr[k]
{
k = j;
}
}
int temp = arr[k];
arr[k] = arr[i];
arr[i] = temp;
}
}
输出:
-43 -21 0 5 12 24 34 56 65 689
贰、堆排序
具体思路:见图解排序算法(三)之堆排序
自己的理解:
1.核心:堆排序就是建立在最大堆,或者最小堆的不断调整的基础上进行排序的。
2.排序思路:以升序,使用最大堆为例
由于最大堆的根,也就是对顶永远都是最大的元素,于是总是将堆顶元素放在数组的最后一个位置,并且不再参与堆的调整。
#include <algorithm>
using namespace std;
/*--------------调整函数---------------*/
void PercDown(int *h,int p,int n){
int parent,child;
int temp;
temp = h[p];
for(parent = p;parent*2 <= n-1;parent = child){
child = parent * 2+1;//数组是从零开始存储元素,所以父子结点对应关系为p=c*2+1.
if((child < n-1) && (h[child]<h[child+1]))//降序改成h[child]>h[child+1])
child++;//限定子结点不能超过元素长度,并且选出子结点中较大的一个
if(temp >= h[child])//降序改成“<=”
break;//如果堆顶元素大于等于子结点,就不做改动
else
h[parent] = h[child];//如果堆顶元素小于子结点,就把子结点放在堆顶
}
h[parent] = temp;//最后把原来的堆顶元素放在没有他更小的位置上
}
void Heap_sort(int *h,int n){
for(int i=n/2;i>=0;i--)//一定是i>=0,因为堆顶为0
PercDown(h,i,n)//虽然是从零开始放元素,但是下标的处理在调整函数中
for(int i=n-1;i>=0;i--){//总是让i为最后一个元素的下标
swap(a[0],a[i]);//总是将最大的元素放置在末尾
PercDown(h,0,i-1);//i-1总是将最后一位排除在堆外,不做变动
}
- 3.几个关键点:
①、调整函数的解释:PercDown(int *h,int p,int n)。h是数组名,p是当前要调整的堆的堆顶的位置,n当前堆的大小
②heap_sort函数中第一个循环,是为了优化算法的空间复杂度,对数组直接转换成堆,第二个循环:就是排序的过程,没排一个元素,就调整一次堆,总是让堆是最大堆或者最小堆。
③升序用最大堆,降序用最小堆;这段代码是升序,降序只需要将PercDown函数中,选择子结点中较大的一个,改成较小的一个;把堆顶元素大于子结点,改成小于子结点即可。
四、归并排序
1.递归实现二路排序:
将数组划分成无数个区间,递归的合并就行。
注意事项:
归并排序的核心思路就是,用一个分割函数,递归的将数组分割成【left,mid】和【mid+1,right】两部分,直到只剩下一个元素,就是分割出来的最小区间,然后就是用合并函数,将两个区间按照一定的顺序合并,然后返回到分割函数。
//合并函数
void merge(int merge[],int L1,int R1,int L2,int R2)
{
int i=L1,j=L2,index=0;
int temp[1000]={};//使用临时数组来排序,暂时保存数据
while(i<=R1 && j<=R2){//由于最小细分单位为1,也就是L1就是下标的情况,所以可能取等
if(merge[i] <= merge[j]){//递归式边界条件,也是升序降序开关。
temp[index++] = merge[i++];
}
else{
temp[index++] = merge[j++];
}
}
while(i<=R1){
temp[index++] = merge[i++];
}
while(j<=R2){
temp[index++] = merge[j++];
}
for(int i=0;i<index;i++){//将排好序的数据存放到原数组
merge[L1+i] = temp[i];
}
}
//分割函数,递归实现
void mergesort(int sort[],int left,int right){
if(left<right){
int mid = (left+right)/2;//就是用这个来分割数组
mergesort(sort,left,mid);
mergesort(sort,mid+1,right);
merge(sort,left,mid,mid+1,right);
}
}
2.非递归实现
思路是:使用步长为2的step表示起始区间,在区间内将其分成左右两部分,并进行和并
step成指数递增
//分割函数非递归实现
void mergesort(int a[],int n){
for(int step=2;step/2<=n;step*=2){
for(int i=1;i<=n;i += step){
int mid = i + step/2 -1;
if(mid + 1 <= n){
merge(a,i,mid,mid+1,min(i+step-1,n));
}
}
}
}
强调:内层循环控制了排序的起始位置,如果采用0为起始下标,i=0;