程序员面试宝典_第13章_数据结构基础_排序算法小结(3)

六、直接选择排序算法

[算法思想]:对比数组中前一个元素跟后一个元素的大小,如果后面的元素比前面的元素小则用一个变量k来记住他的位置,接着第二次比较,前面“后一个元素”现变成了“前一个元素”,继续跟他的“后一个元素”进行比较如果后面的元素比他要小则用变量k记住它在数组中的位置(下标),等到循环结束的时候,我们应该找到了最小的那个数的下标了,然后进行判断,如果这个元素的下标不是第一个元素的下标,就让第一个元素跟他交换一下值,这样就找到整个数组中最小的数了。然后找到数组中第二小的数,让他跟数组中第二个元素交换一下值,以此类推。

选择排序法的第一层循环从起始元素开始选到倒数第二个元素,主要是在每次进入的第二层循环之前,将外层循环的下标赋值给临时变量,接下来的第二层循环中,如果发现有比这个最小位置处的元素更小的元素,则将那个更小的元素的下标赋给临时变量,最后,在二层循环退出后,如果临时变量改变,则说明,有比当前外层循环位置更小的元素,需要将这两个元素交换.

 

#include<iostream>
using namespace std;

void SelectionSort(int R[],int len)//R(0..n-1)是待排序的文件
 {    

int i,j,Temp,MinIndex;


	for(i=0;i<len-1;i++) 
	{ 
        	MinIndex=i;
		for(j=i+1;j<len;j++)
		{
			if(R[j]<R[MinIndex])
			{
		    MinIndex=j;
			
			}

		}
	        Temp=R[i];
			R[i]=R[MinIndex];
			R[MinIndex]=Temp;	


	} 


} 

int main()
{
	
	int a[10]={5,8,4,78,12,96,1,4,16,50};
	int i;
	for(i=0;i<10;i++)
	cout<<a[i]<<" ";
	cout<<endl; 
    SelectionSort(a,10);
	for(i=0;i<10;i++)
	cout<<a[i]<<" ";
	cout<<endl; 
system("pause");
return 0;
}

选择排序是给每个位置选择当前元素最小的,比如给第一个位置选择最小的,在剩余元素里面给第二个元素选择第二小的,依次类推,直到第n-1个元素,第n个元素不用选择了,因为只剩下它一个最大的元素了。那么,在一趟选择,如果一个元素比当前元素小,而该小的元素又出现在一个和当前元素相等的元素后面,那么交换后稳定性就被破坏了。比较拗口,举个例子,序列5 8 5 2 9,我们知道第一遍选择第1个元素5会和2交换,那么原序列中2个5的相对前后顺序就被破坏了,所以选择排序不是一个稳定的排序算法。

七、堆排序算法

二叉堆的定义:二叉堆是完全二叉树或者是近似完全二叉树。

二叉堆满足二个特性:

1.父结点的键值总是大于或等于(小于或等于)任何一个子节点的键值。

2.每个结点的左子树和右子树都是一个二叉堆(都是最大堆或最小堆)。

当父结点的键值总是大于或等于任何一个子节点的键值时为最大堆。当父结点的键值总是小于或等于任何一个子节点的键值时为最小堆。下图展示一个最小堆:

堆的存储:一般都用数组来表示堆,i结点的父结点下标就为(i – 1) / 2。它的左右子结点下标分别为2 *i+ 12 *i+ 2。如第0个结点左右子结点下标分别为12

堆化数组:

对叶子结点来说,可以认为它已经是一个合法的堆了即2060 65 4 49都分别是一个合法的堆。只要从A[4]=50开始向下调整就可以了。然后再取A[3]=30A[2] = 17A[1] = 12A[0] = 9分别作一次向下调整操作就可以了。下图展示了这些步骤:

 

[算法思想]:对于最小堆,当堆建好之后堆中第0个数据是堆中最小的数据。取出这个数据再执行下堆的删除操作。这样堆中第0个数据又是堆中最小的数据,重复上述步骤直至堆中只有一个数据时就直接取出这个数据。由于堆也是用数组模拟的,故堆化数组后,第一次将A[0]A[n - 1]交换,再对A[0…n-2]重新恢复堆。第二次将A[0]A[n – 2]交换,再对A[0…n - 3]重新恢复堆,重复这样的操作直到A[0]A[1]交换。由于每次都是将最小的数据并入到后面的有序区间,故操作完成后整个数组就有序了。有点类似于直接选择排序注意使用最小堆排序后是递减数组,要得到递增数组,可以使用最大堆。

最小堆排序代码如下:

 

#include<iostream>
using namespace std;
void HeapAdjust(int R[],int parent,int len)
{
   int temp=R[parent];//temp保存当前父节点
   int child=2*parent+1;//得到左孩子
   while(child<len)
   {
   if(child+1<len&&R[child+1]<R[child])//如果parent有右孩子,则要判断左孩子是否小于右孩子
	   ++child;
   if(temp<R[child])//父亲节点小于子节点,就不用做交换
	   break;
   R[parent]=R[child];//将较小子节点的值赋给父亲节点
   parent=child;//然后将子节点做为父亲节点
   child=2*parent+1;//找到该父亲节点的左孩子节点
   }

   R[parent]=temp;//最后将temp值赋给子节点,插入完成

}

void HeapSort(int R[],int len)//R(0..len-1)是待排序的文件
 {  
    int i,j,Temp;
    for(i=len/2-1;i>=0;i--) //(len/2-1)就是堆中父节点的个数
	{ 
		HeapAdjust(R,i,len);
	} 

    for(j=len-1;j>0;j--) 
	{ 
		Temp=R[0];
		R[0]=R[j];
		R[j]=Temp;
		HeapAdjust(R,0,j);
	} 
} 

int main()
{
	
	int a[10]={5,8,4,78,12,96,1,4,16,50};
	int i;
	for(i=0;i<10;i++)
	cout<<a[i]<<" ";
	cout<<endl; 
    HeapSort(a,10);
	for(i=0;i<10;i++)
	cout<<a[i]<<" ";
	cout<<endl; 
system("pause");
return 0;
}

 

 

 最大堆排序算法如下:

#include<iostream>
using namespace std;
void HeapAdjust(int R[],int parent,int len)
{
   int temp=R[parent];//temp保存当前父节点
   int child=2*parent+1;//得到左孩子
   while(child<len)
   {
   if(child+1<len&&R[child+1]>R[child])//如果parent有右孩子,则要判断左孩子是否大于右孩子
	   ++child;
   if(temp>R[child])//父亲节点大于子节点,就不用做交换
	   break;
   R[parent]=R[child];//将较小子节点的值赋给父亲节点
   parent=child;//然后将子节点做为父亲节点
   child=2*parent+1;//找到该父亲节点的左孩子节点
   }

   R[parent]=temp;//最后将temp值赋给子节点,插入完成

}

void HeapSort(int R[],int len)//R(0..len-1)是待排序的文件
 {  
    int i,j,Temp;
    for(i=len/2-1;i>=0;i--) //(len/2-1)就是堆中父节点的个数
	{ 
		HeapAdjust(R,i,len);
	} 

    for(j=len-1;j>0;j--) 
	{ 
		Temp=R[0];
		R[0]=R[j];
		R[j]=Temp;
		HeapAdjust(R,0,j);
	} 
} 

int main()
{
	
	int a[10]={5,8,4,78,12,96,1,4,16,50};
	int i;
	for(i=0;i<10;i++)
	cout<<a[i]<<" ";
	cout<<endl; 
    HeapSort(a,10);
	for(i=0;i<10;i++)
	cout<<a[i]<<" ";
	cout<<endl; 
system("pause");
return 0;
}


 

八、桶排序算法

[算法思想]:将阵列分到有限数量的桶子里。每个桶子再个别排序(有可能再使用别的排序算法或是以递回方式继续使用桶排序进行排序)。桶排序是鸽巢排序的一种归纳结果。当要被排序的阵列内的数值是均匀分配的时候,桶排序使用线性时间(Θ(n))。但桶排序并不是 比较排序,他不受到 O(n log n) 下限的影响。
      例如要对大小为[1..1000]范围内的n个整数A[1..n]排序,可以把桶设为大小为10的范围,具体而言,设集合B[1]存储[1..10]的整数,集合B[2]存储(10..20]的整数,……集合B[i]存储((i-1)*10, i*10]的整数,i = 1,2,..100。总共有100个桶。然后对A[1..n]从头到尾扫描一遍,把每个A[i]放入对应的桶B[j]中。 然后再对这100个桶中每个桶里的数字排序,这时可用冒泡,选择,乃至快排,一般来说任何排序法都可以。最后依次输出每个桶里面的数字,且每个桶中的数字从小到大输出,这样就得到所有数字排好序的一个序列了。  
      假设有n个数字,有m个桶,如果数字是平均分布的,则每个桶里面平均有n/m个数字。如果对每个桶中的数字采用快速排序,那么整个算法的复杂度是O(n+m*n/m*log(n/m))=O(n+nlogn-nlogm)  
      从上式看出,当m接近n的时候,桶排序复杂度接近O(n),当然,以上复杂度的计算是基于输入的n个数字是平均分布这个假设的。这个假设是很强的,实际应用中效果并没有这么好。如果所有的数字都落在同一个桶中,那就退化成一般的排序了。

例如待排数字[6 2 4 1 5 9]

准备3个空桶,最大数个空桶(max=9,min=1,num=(max-min+1)/3=3)

[6 2 4 1 5 9]           待排数组

[ 0 0 0 ][0 0 0][ 0 0 0]   空桶

    (1 )     ( 2)    ( 3)    桶编号

1,顺序从待排数组中取出数字,首先6被取出,然后把6入6号桶;

[6 2 4 1 5 9]           待排数组

[0 0 0][6 0 0 ][ 0 0 0]   空桶

    (1)     (2)       (3)   桶编号

2,顺序从待排数组中取出下一个数字,此时2被取出,将其放入2号桶,是几就放几号桶

[6 2 4 1 5 9]           待排数组

[2 0 0 ][6 0 0 ][ 0 0 0]   空桶

     (1)     (2)      (3)   桶编号

3,4,5,6省略,过程一样,全部入桶后变成下边这样

[6 2 4 1 5 9]           待排数组

2 1 0 ][6 4 5 ][9 0 0]   空桶

3,分别对每个桶进行排序

[6 2 4 1 5 9]           待排数组

[1 2] [4 5 6][9]        排序后的桶

顺序取出即可:1 2 4 5 6 9

#include<iostream>
using namespace std;
class node{
public: 
	int data;
	node* next;
};

void BucketSort(int A[],int len,int Bucket_size)//Bucket_size为桶的大小
{    
	int min,max,num,pos;
	min=A[0];
	max=A[0];
	for (int i=0;i<len;i++)
	{
		if (A[i]<min)
		{
			min=A[i];
		} 
		else if(A[i]>max)
		{
			max=A[i];
		}	
	}
	num=(max-min+1)/Bucket_size+1;//num为桶的数量
	node** Bucket_table=new node*[num];
	for (int i=0;i<num;i++)
	{
		Bucket_table[i]=new node;
		Bucket_table[i]->data=0;
		Bucket_table[i]->next=NULL;

	}

	for (int j=0;j<len;j++)
	{
		pos=A[j]/Bucket_size;//计算桶号
		node* p=new node;
		p->data=A[j];
		p->next=NULL;
		node* q=Bucket_table[pos];
		if (q->next==NULL)
		{
			Bucket_table[pos]->next=p;
			(Bucket_table[pos]->data)++;//记录当前桶中的数据量 
		} 
		else
		{
			while(q->next!=NULL && q->next->data < p->data)
			{
				q=q->next;
			}

			p->next=q->next;
			q->next=p;
			(Bucket_table[pos]->data)++;
		}	
	}
	int m=0;
	for (int i=0;i<num;i++)
	{
		for (node* p=Bucket_table[i]->next;p!=NULL;p=p->next)
		{
			A[m++]=p->data;
		}		
	}
	for (int i=0;i<num;i++)
	{
		delete [] Bucket_table[i];
	}

	delete [] Bucket_table;




} 
int main()
{
	int a[11]={5,8,4,78,12,56,96,1,4,16,50};
	int* temp=new int[11];
	int i;
	for(i=0;i<11;i++)
		cout<<a[i]<<" ";
	cout<<endl; 
	BucketSort(a,11,10);
	delete [] temp;
	for(i=0;i<11;i++)
		cout<<a[i]<<" ";
	cout<<endl; 

	system("pause");
	return 0;
}


 

  

九、基数排序算法

[算法思想]:基数排序的发明可以追溯到1887年赫尔曼·何乐礼在打孔卡片制表机(Tabulation Machine)上的贡献。它是这样实现的:将所有待比较数值(正整数)统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后, 数列就变成一个有序序列。基数排序的方式可以采用LSD(Least significant digital)或MSD(Most significant digital),LSD的排序方式由键值的最右边开始,而MSD则相反,由键值的最左边开始。

最高位优先(Most Significant Digit first)法,简称MSD法:先按k1排序分组,同一组中记录,关键码k1相等,再对各组按k2排序分成子组,之后,对后面的关键码继续这样的排序分组,直到按最次位关键码kd对各子组排序后。再将各组连接起来,便得到一个有序序列。

最低位优先(Least Significant Digit first)法,简称LSD法:先从kd开始排序,再对kd-1进行排序,依次重复,直到对k1排序后便得到一个有序序列。

算法分析:

平均时间复杂度:O(dn)(d即表示整形的最高位数)

空间复杂度:O(n)  

稳定性:稳定

   算法举例:

比如:我们有如下欲排数据序列:

1012312412

下面选择LSD逻辑演示

第一次按个位数值分配,结果如下图所示:

010   
131   
21212  
3    
424   

 

然后收集数据如下:

1031121224

第二次按十位数值分配,结果如下图所示:

0    
1101212 
224   
331   
4    

然后收集数据如下:

1012122431

 注意:分配时是从欲排数据序列的末位开始进行,逐次分配至首位。

 

 

#include<iostream>
using namespace std;
class node{
public: 
	int data;
	node* next;
};
int GetNumPos(int num,int pos)// 找到num的从低到高的第pos位的数据 

{   int temp=1;
	for (int i=1;i<pos;i++)
	{
		temp*=10;
	}
	return (num/temp)%10;

}
int FindMaxNumPos(int A[],int len) //获取数组中最大数的位数

{   int max=A[0];
	for (int i=0;i<len;i++)
	{
		if (A[i]>max)
		{
			max=A[i];
		}
	}
	int count=0;
	int temp=max;
	while (temp!=0)
	{
		count++;
		temp/=10;
	}

return count;

}

void RadixSort(int A[],int len,int m_pos)
{    
	int maxpos,num,pos;
	maxpos=m_pos;
	num=10;//num为桶的大小,0~9
	node** Radix_table=new node*[num];

	
	for (int p=1;p<=maxpos;p++)
	{
		for (int i=0;i<num;i++)
		{
			Radix_table[i]=new node;
			Radix_table[i]->data=0;
			Radix_table[i]->next=NULL;

		}
		for (int j=0;j<len;j++)
		{
			pos=GetNumPos(A[j],p);//计算桶号,即第p位数对应的桶号
			node* p_node=new node;
			p_node->data=A[j];
			p_node->next=NULL;
			node* q_node=Radix_table[pos];
			if (q_node->next==NULL)
			{
				Radix_table[pos]->next=p_node;
				(Radix_table[pos]->data)++;//记录当前桶中的数据量 
			} 
			else
			{
				while(q_node->next!=NULL)
				{
					q_node=q_node->next;
				}
				 q_node->next=p_node;
				(Radix_table[pos]->data)++;
			}	
		}
		int m=0;
		for (int i=0;i<num;i++)
		{
			for (node* p=Radix_table[i]->next;p!=NULL;p=p->next)
			{
				A[m++]=p->data;
			//	Radix_table[i]->data=0;
			}		
		}
		for (int i=0;i<num;i++)
		{
			delete [] Radix_table[i];
		}


	}
	

delete [] Radix_table;




} 
int main()
{
	int a[11]={5,8,4,78,12,56,96,1,4,16,50};
	int* temp=new int[11];
	int i;
	for(i=0;i<11;i++)
		cout<<a[i]<<" ";
	cout<<endl; 
	int mpos=FindMaxNumPos(a,11);
	RadixSort(a,11,mpos);
	delete [] temp;
	for(i=0;i<11;i++)
		cout<<a[i]<<" ";
	cout<<endl; 

	system("pause");
	return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值