算法笔记---算法初步之排序

内部排序:

一、插入排序

							**壹.直接插入排序**

自己的理解:
插入排序是将数组当成摸牌
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)
 {
 	forint 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;

五、基数排序

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值