常用排序算法-冒泡,快排,选择排序,堆排序

在计算机中,由于数据的形式,数量和保存形式不同,对数据进行排序的方法也不同.按照排序过程中数据保存的形式不同,分为内部排序和外部排序两大类.

        内部排序:

        整个排序过程中不需要访问外存:

1.       交换排序: 冒泡排序,快速排序

2.       选择排序: 直接选择排序,堆排序

3.       插入排序: 直接擦汗如排序,希尔排序

4.       合并排序

外部排序:

在适用内部排序的时候,所有待处理的数据都已经调入计算机内存,在排序操作中可以直接使用,但是计算机内存容量是有限的,当对大批量的数据进行排序的时候,不可能一次性将数据全部转入内存,这时可见外存中的数据读取一部分到内存,将内存中的数据排序之后,再存储到外存中,然后再在外存中读取下一部分需要排序的数据. 这样排序数据在内存和外存之间多次数据交换,达到排序整个数据的目的,这就是外部排序



冒泡排序

是一种对相邻数据进行数据交换的排序方法,时间复杂度:O(n2)

基本思想:对关键字逆序从下往上不断扫描,当发现相邻两个关键字的次序与排序规则要求的不一致的时候,将这两个记录进行交换。就像水泡上升一样。

        存在问题:当排序已经满足规则了,但是循环还没完,程序并不知道后面的数据是不是有序的,那么就会造成浪费。

        改进方法:就是在每一次循环的时候,检查这一次循环的过程是否发生了数据交换,如果没有就直接退出整个循环,这里就用flag这个变量来表示是否发生了数据交换,这就实现了用空间来换取时间的目的。

快速排序(nlog(n)-n2)

        快速排序是对冒泡排序的另外一种改进,其思想是:通过一遍排序将要排序的数据划分为两部分,其中一部分数据比另外一部分小,然后再对这两部分分别再进行类似的排序,一直到每一部分都只剩下一个数或者为空的时候,整个排序算法就结束。

        思想精华:用到了二分以及分治的思想,可以将大批的数据逐步分解。

        具体步骤:

        快速排序要进行的第一步,也是最重要的一步就是将数组先分成两部分,一部分数据比另一部分大或者小。

        首先对一组无序的数组,随便选取一个元素作为基准值,一般都选第一个(下标为0)。

        然后从后向前对每一个元素和这个基准元素进行比较,知道找到一个比基准元素小的元素,则将这个元素和基准元素的位置互换(这就有点像冒泡排序里面的操作了)。

        接下来就就从数组的开头向后,将每个元素和基准元素相比,找到第一个大于基准元素的值与其交换位置。(这里是不可能找到刚才调换后的基准值的位置之后的,因为已经确定了基准值之后的已经是小于基准值的了,所以这次从前往后,只要找到比基准值大的就和基准值交换位置,也是为了保证在基准值的后面的元素都比基准值大,在基准值前面的元素都比基准值小)。

        上面两步就可以将这个无序的数组分成了两部分,但是这样子仅仅满足了一边大一边小。所以对这两部分分别再进行以上操作。

简单选择排序算法

        这个算法思路和实现都比较简单,就是首先从数据中选择一个最小的数据和第一个数据进行交换,接下来就从剩下来的数据中执行刚才一样的操作。

堆排序算法(nlog(n))

        它也是一种选择排序算法。这个算法利用到了堆的特性。

        什么是堆?

        堆其实就是一个完全二叉树,树中每个节点对应原始数据的一个记录,并且每个节点满足一下条件:非叶子节点的数据大于或者等于左右子节点的数据。根据堆的定义,根节点是最大值。所以堆排序的过程分为两步:

1.       将无序的数据构成堆

2.       利用堆排序

        对于一颗完全二叉树来说,可以用数组的的下标来表示其在二叉树中的位置,对于任意节点i,只要不是根节点,其父节点编号为i/2,其左子节点的编号为2i+1,右子节点编号为2i+2.给一个图可能看得更清楚。


.

可以看到如果用数组的下标来表示其在完全二叉树中的位置的时候,相邻下标的数组元素并一定是在二叉树中位置相邻。理解这一点很重要。

堆排序算法的第一步就是要改造将一组数组数据构成堆,堆的定义也说过了,就是对于任何一个非叶子节点,其值要大于或者等于左右子节点。

步骤就是:

1)  从第一个非叶子节点(n/2-1),比较这个节点的左右子节点的大小,如果有子节点大的话,就用根节点(当前节点)和(当前节点的)右子节点进行比较。

2)  如果有子节点比根节点大的话,则就替换根节点和右子节点的位置。

3)  但是这样的话就影响了原来右子节点作为根节点的那一个堆,那么就需要对这个堆重新整理。

构造好堆之后,利用根节点是最大值的原理,将根节点和末尾元素替换,并将根节点存在数组的最后。


下面是各个算法的实现

#include<stdio.h>
#include<stdlib.h>
#include<time.h> 

int Rand(int arr[],int n,int start,int end){
	int flag,i,j;
	srand(time(NULL));	//随机数生成器初始化函数
	if(end-start+1<n)
		return  0;
	for(i=0;i<n;i++){
		do{
			arr[i]=rand()%(end-start+1); //将生成的随机数存入到数组 			
			flag=0;
			//如果生成的随机数是重复的话,那就重新再生成一个
			for(j=0;j<i;j++){
				if(arr[j]==arr[i])
					flag=1;
			}			
		}while(flag);
	}
} 
void Print(int arr[],int n){
	int i;
	for(i=0;i<n;i++){
		printf("%d\n",arr[i]);
	}
	return ;
}


//冒泡排序的原始算法 
void Bubble(int arr[],int n){
	int i,j,tmp;
	time_t now,end;
	now=time(NULL);	
	for(i=0;i<n;i++){
		//这里其实就是冒泡排序的最原始的优化,第一次循环其实已经把最大的数排上去了,所以
		//之后每次循环所需要比较的数就减 1 了 
		for(j=n-1;j>i;j--){
			//如果相邻两个数 的排序规则不满足要求的话就替换 
			if(arr[j]>arr[j-1]){
				tmp=arr[j];
				arr[j]=arr[j-1]; 
				arr[j-1]=tmp;
			}
		}
	}
	end=time(NULL);
	printf("我用时%d",end-now); 
}


//冒泡排序的改进算法 
void Bubble_Pro(int arr[],int n){
	int i,j,tmp,flag;
	time_t now,end;
	now=time(NULL);	
	for(i=0;i<n;i++){
		//这里其实就是冒泡排序的最原始的优化,第一次循环其实已经把最大的数排上去了,所以
		//之后每次循环所需要比较的数就减 1 了 
		for(j=n-1;j>i;j--){
			//如果相邻两个数 的排序规则不满足要求的话就替换 
			if(arr[j]>arr[j-1]){
				tmp=arr[j];
				arr[j]=arr[j-1]; 
				arr[j-1]=tmp;
			}
			
		// flag来标志某次查询是不是进行了数据变换,这样的话就多用到了一个变量空间,就是以空间来换时间 
			flag=1;
		}
		if(flag==0)
			break;
	}
	end=time(NULL);
	printf("改进之后用时%d",end-now); 
} 

//快速排序算法 --数组切割

int Div(int arr[],int left,int right){
	int base=arr[left];
	while(left<right){
		while(left<right && arr[right]>base)
			--right;
		arr[left]=arr[right];
		while(left<right && arr[left]<base)
			++left;
		arr[right]=arr[left];
	}
	arr[left]=base;
	return left;
} 

void Quick(int arr[],int left,int right){
	int i,j;
	if(left<right){
		i=Div(arr,left,right);
		Quick(arr,left,i-1);
		Quick(arr,i+1,right);
	}
}

//堆排序算法-----将无序数组构成堆 
void Heap(int arr[],int s,int n){
	int j,t; 
	//s表示当前节点,2*s+1就是当前节点的左子节点 ,满足这个条件也就是说有右子节点 
	while(2*s+1<n){
		j=2*s+1;
		//j+1<n是要判断有没有右子树 
		//如果右子树大于左子树的话,就比较右子树 
		if((j+1)<n){
			if(arr[j]<arr[j+1])
				j++; 
		}
		//这个时候左子树小于右子树,于是就需要比较当前节点与其右子树即可 ,如果根节点小于右子树则需要交换 
		if(arr[s]<arr[j]){
			t=arr[s];
			arr[s]=arr[j];
			arr[j]=t;
			
			//这个时候堆的数据被破坏了,可能会破话本来以arr[j]为根的堆结构,所以要对其重新调整 
			s=j; 
		}else
			break;
	}
	
	//经过一次构造堆的操作,可以确定堆的根节点是整个堆的最大值 
} 


// 堆排序
void HeapSort(int arr[],int n){
	int i,t;
	for(i=n/2-1;i>=0;i--){
		//将arr[0]到arr[n-1]构成堆 
		Heap(arr,i,n);
	}
	
	for(i=n-1;i>0;i--){
		t=arr[0];
		arr[0]=arr[i];
		arr[i]=t;
		Heap(arr,0,i);
	} 
	
} 

int main(){
	int a[100];
	Rand(a,10,0,9);
	Print(a,10);
//	printf("*********************快速排序之后********************************\n"); 
//	Quick(a,0,9);
	printf("*********************堆排序之后********************************\n"); 
	Quick(a,0,9);
	Print(a,10);
	//对a数组进行冒泡排序
//	printf("*********************冒泡排序之后********************************\n"); 
	//Bubble(a,10);	
}









猜你喜欢

转载自blog.csdn.net/vfi7018/article/details/80659564