排序.docx
《排序.docx》由会员分享,可在线阅读,更多相关《排序.docx(29页珍藏版)》请在冰豆网上搜索。
排序
排序
1.排序算法优劣:
●空间复杂度:
需要的辅助内存
●时间复杂度:
关键字的比较次数以及移动次数,消耗总时间
●稳定性:
若A和B的关键字相同,但排序后A、B的先后次序保持不变,则称这种算法是稳定的;反之,则是不稳定的
2.分类
●内部排序:
整个排序过程不需要借助外部存储器,所有排序操作都在内存中完成。
●外部排序:
如果需要进行排序的数据量比较庞大,在内存中不能一次性注入所有的数据,需要借助于外部存储器,这样的排序称为外部排序,外部排序最常用的算法是多路归并排序,即将源文件分解为多个能够一次性注入到内存中的小文件,将比较结果在两两比较,依次,直到比较出最终结果。
我们平时说的排序都是指的内部排序。
3.内部排序分类
3.1选择排序
3.1.1直接选择排序
算法思想:
需要经过n-1次比较。
第一次比较:
程序将记录定位在第一个数据,拿第一个数据依次和后面每个数据进行比较,如果第一个数据大于后面的某个数据,交换他们……依次类推。
经过第一次比较,这些数据中的最小值就被选出,放在第一位。
第二次比较:
程序将记录定位在第二个数据,让第二个数据依次和后面的每个数据进行比较,如果第二个数据比后面的某个数据大,则交换它们,依次类推。
经过第二次比较,这些数据中第二小的数据就被选出,并且排在第二位。
按照此规则一共进行n-1次比较。
对应的java代码,此处一共两个类:
DataSource类(后面的排序数据的封装都是用的此类):
packagesuanfa;
/*
*定义一个数据封装包
**/
publicclassDataSourceimplementsComparable{
privateintdata;
privateStringflag;
publicintgetData(){
returndata;
}
publicvoidsetData(intdata){
this.data=data;
}
publicStringgetFlag(){
returnflag;
}
publicvoidsetFlag(Stringflag){
this.flag=flag;
}
publicDataSource(intdata,Stringflag){
this.data=data;
this.flag=flag;
}
publicStringtoString(){
returndata+flag;
}
@Override
publicintcompareTo(DataSourceo){
returnthis.data>o.data?
1:
(this.data==o.data?
0:
-1);
}
}
SelectSort类:
packagesuanfa;
importjava.util.Arrays;
publicclassSelectorSort{
publicstaticvoidselectSort(DataSource[]datas){
System.out.println("开始排序:
");
intarrayLength=datas.length;
for(inti=0;ifor(intj=i+1;jif(datas[i].compareTo(datas[j])>0){
DataSourcetmp=datas[i];
datas[i]=datas[j];
datas[j]=tmp;
}
}
System.out.println(Arrays.toString(datas));
}
}
publicstaticvoidmain(String[]args){
DataSource[]datas={newDataSource(21,""),newDataSource(30,""),
newDataSource(49,""),newDataSource(30,"*"),
newDataSource(16,""),newDataSource(9,""),
newDataSource(27,"")};
System.out.println("排序之前:
\n"+Arrays.toString(datas));
selectSort(datas);
System.out.println("排序之后:
\n"+Arrays.toString(datas));
}
}
输出结果如下:
直接选择排序时不稳定,从两个30就可以看出。
优化:
由于直接排序算法的意图就是每一次找出其中的最小值,所以我们没必要后面的数只要比第一个数小就要进行交换,我们只需要找到每次后面的最小数就行,我们可以假定最小数为第一个,即将第一个数的索引赋值给一个变量,然后和后面的数进行比较,如果后面的数小于第一个数,就将后面数的索引赋给之前定义的变量。
比较完之后假如索引数仍未第一个数的索引,则说明最小数就是第一个,不用改变。
假如索引发生变化,则将后面的数与第一个数进行交换。
然后进行下一次的比较,我们可以将上面红色的代码修改为:
for(inti=0;iintminIndex=i;
for(intj=i+1;jif(datas[minIndex].compareTo(datas[j])>0){
minIndex=j;
}
}
if(minIndex!
=i){
DataSourcedata=datas[minIndex];
datas[minIndex]=datas[i];
datas[i]=data;
}
System.out.println(Arrays.toString(datas));
}
3.1.2堆排序
3.1.2.1概念
假如有n个数据元素的序列k0,k1,…,kn-1,当且仅当满足如下关系时,可以将这组数据称为小顶堆(小根堆)。
Ki<=K2i+1且Ki<=K2i+2(其中i=0,2,…,(n-1)/2)
若将上述表达式中的小于等于换为大于等于,则这组数据称为大顶堆(大根堆)。
假如将小顶堆或者大顶堆的数据元素排列为完全二叉树的话,对于小顶堆,树中所有节点的值都小于其左右节点的值,此树的根节点的值最小。
对于大顶堆则完全相反。
堆排序的关键是建堆。
3.1.2.2排序过程
Ø第一趟将索引0~n-1处的全部数据建立大顶堆或者小顶堆,就可以选择出这组数组中的最大值或者最小值,然后将最大值或者最小值与数组中的最后一个节点交换,这样最大值或者最小值就排在最后。
Ø第二趟将索引0~n-2处的全部数据建立大顶堆或者小顶堆,然后选择出第二大或者第二小的数据,然后将选择出的数据与数组中倒数第二个节点进行交换,这样第二大或者第二小的数据就排在倒数第二位。
Ø……
Ø第k趟将索引0~n-k处的全部数据建立大顶堆或者小顶堆,就可以选择出剩余数据中的最大值或者最小值,然后将大顶堆或者小顶堆的根节点与这组数据中的倒数第k个节点交换,使得这组数据的最大值或者最小值排在倒数第k位。
由于建堆的过程就是选取最大值或者最小值,因此堆排序的实质就是选择排序。
堆排序与直接选择排序的区别:
堆排序通过树形结构保存部分比较结果,可减少比较次数,从而提高效率。
而直接选择排序第一次需要进行n-1次比较,第二次需要进行n-2次比较,在n-2次比较中,有许多比较有可能在前面的n-1次比较中已经做过,但是由于前面的比较未能保留比较结果,所以后面的比较就进行了重复性的工作,从而使得效率降低。
3.1.2.3建堆过程
以建立大顶堆为例。
始终重复这一过程:
从最后一个非叶子节点开始,比较该节点和它的两个子节点的值,如果某个子节点的值大于父节点的值,把父节点和较大的子节点进行交换。
向前逐步调整直到根节点,即保证每个父节点的值都大于等于其左右两个子节点的值。
3.1.2.4源代码
packagesuanfa;
importjava.util.Arrays;
publicclassHeapSort
{
//交换data数组中索引i与j处的值
privatestaticvoidswap(DataSource[]data,inti,intj)
{
DataSourcetmp=data[i];
data[i]=data[j];
data[j]=tmp;
}
//对data数组从0到lastIndex建大顶堆
privatestaticvoidbuildMaxHeap(DataSource[]data,intlastIndex)
{
for(inti=(lastIndex-1)/2;i>=0;i--)
{
//保存当前正在判断的节点
intk=i;
//如果当前K节点的子节点存在
while(k*2+1<=lastIndex)
{
//k节点的左子节点的索引
intbiggerIndex=2*k+1;
//如果biggerIndex小于lastIndex,即biggerIndex+1
//代表的k节点的右子结点存在
if(biggerIndex{
if(data[biggerIndex].compareTo(data[biggerIndex+1])<0)
{
//biggerIndex总是记录较大子节点的索引
biggerIndex++;
}
}
//如果K节点的值小于其较大子节点的值
if(data[k].compareTo(data[biggerIndex])<0)
{
//交换它们
swap(data,k,biggerIndex);
//将biggerIndex赋值给k,开始while循环的下一个循环
//重新保证k节点的值大于其左右两个子节点的值
k=biggerIndex;
}else
{
break;
}
}
}
}
publicstaticvoidheapSort(DataSource[]data)
{
System.out.println("开始排序");
intarrayLength=data.length;
for(inti=0;i{
//建大顶堆
buildMaxHeap(data,arrayLength-1-i);
swap(data,0,arrayLength-1-i);
System.out.println(Arrays.toString(data));
}
}
publicstaticvoidmain(String[]args)
{
DataSource[]data={newDataSource(21,""),newDataSource(30,""),
newDataSource(49,""),newDataSource(30,"*"),
newDataSource(21,"*"),newDataSource(16,""),
newDataSource(9,"")};
System.out.println("排序之前:
\n"+Arrays.toString(data));
heapSort(data);
System.out.println("排序之后:
\n"+Arrays.toString(data));
}
}
排序结果:
根据结果可知,堆排序是不稳定的。
3.2交换排序
3.2.1冒泡排序
基本思想:
对于一组包含n个数据的一组记录,最坏的情况下,冒泡排序需要进行n-1趟比较。
第1趟:
依次比较0和1、1和2、2和3……n-2和n-1索引的元素,如果发现第一个元素大于后一个元素,交换它们。
经过第一趟,最大的元素排在后边。
后面重复上述过程。
3.2.1.1源代码
packagesuanfa;
importjava.util.Arrays;
publicclassBubbleSort
{
publicstaticvoidbubbleSort(DataSource[]data)
{
System.out.println("开始排序");
intarrayLength=data.length;
for(inti=0;i{
booleanflag=false;
for(intj=0;j{
if(data[j].compareTo(data[j+1])>0)
{
DataSourcetmp=data[j];
data[j]=data[j+1];
data[j+1]=tmp;
flag=true;
}
}
System.out.println(Arrays.toString(data));
//如果某趟没有发生交换,则表明已经处于有序状态
if(!
flag)
{
break;
}
}
}
publicstaticvoidmain(String[]args)
{
DataSource[]data={newDataSource(9,""),newDataSource(16,""),
newDataSource(21,"*"),newDataSource(23,""),
newDataSource(30,""),newDataSource(49,""),
newDataSource(21,""),newDataSource(30,"*")};
System.out.println("排序之前:
\n"+Arrays.toString(data));
bubbleSort(data);
System.out.println("排序之后:
\n"+Arrays.toString(data));
}
}
结果显示:
根据结果显示冒泡排序是稳定的。
3.2.2快速排序
基本思想:
从待排的数据序列中任取一个数据(如第一个数据)作为分界值,所有比它小的数值都放在左边,所有比它大的数值都放在右边。
经过这一趟下来,所有的数据分为两个序列,左边序列的值比分界值小,右边序列的值比分界值大。
然后再对左右两个子序列进行递归,对两个子序列重新选择中心元素并依此规则调整,直到每个子表的元素只剩一个,排序完成。
关键:
Ø选取分界值
Ø将所有比分界值小的元素都放在左边
Ø将所有比分界值大的元素都放在右边
实现关键步骤中的第2、3步的思路:
Ø定义一个i变量,i变量从左边第一个索引开始,找大于分界值的元素的索引,并用i来记录它。
Ø定义一个j变量,j变量从右边第一个索引开始,找小于分界值的元素索引,并用j来记录它。
Ø如果iØ重复上述过程,直到i>=j,可以判断j左边的元素都小于分界值,j右边的元素都大于分界值,最后将分界值和j索引处的元素交换即可。
3.2.2.1源代码
packagesuanfa;
importjava.util.Arrays;
publicclassQuickSort
{
privatestaticvoidswap(DataSource[]data,inti,intj)
{
DataSourcetmp=data[i];
data[i]=data[j];
data[j]=tmp;
}
//对data数组中从start~end索引范围的子序列进行处理
//使之满足所有小于分界值的放在左边,所有大于分界值的放在右边
privatestaticvoidsubSort(DataSource[]data,intstart,intend)
{
//需要排序
if(start{
//以第一个元素作为分界值
DataSourcebase=data[start];
inti=start;
intj=end+1;
while(true)
{
//找到小于分界值的元素的索引,或者i已经到了end处
while(i=0);
//找到大于分界值的元素的索引,或者j已经到了start处
while(j>start&&data[--j].compareTo(base)<=0);if(i{
swap(data,i,j);
}else
{
break;
}
}
swap(data,start,j);
//递归左子序列
subSort(data,start,j-1);
//递归右子序列
subSort(data,j+1,end);
}
}
privatestaticvoidquickSort(DataSource[]data)
{
subSort(data,0,data.length-1);
}
publicstaticvoidmain(String[]args)
{
DataSource[]data={newDataSource(9,""),newDataSource(-16,""),
newDataSource(21,"*"),newDataSource(23,""),
newDataSource(-30,""),newDataSource(-49,""),
newDataSource(21,""),newDataSource(30,"*"),
newDataSource(13,"*")};
System.out.println("排序之前:
\n"+Arrays.toString(data));
quickSort(data);
System.out.println("排序之后:
\n"+Arrays.toString(data));
}
}
结果显示:
3.3插入排序
3.3.1直接插入排序
基本思想:
依次将待定排序的数据元素按其关键字的大小插入前面的有序序列。
第一趟插入:
将第二个元素插入前面的有序子序列中----此时前面只有一个元素,必须有序。
第二趟插入:
将第三个元素插入前面的有序子序列中-----前面2个元素是有序的。
…………….
第n-1趟插入:
将第n个元素插入到前面的有序子序列中------前面n-2个元素师有序的。
3.3.1.1源代码
packagesuanfa;
importjava.util.Arrays;
publicclassInsertSort
{
privatestaticvoidinsertSort(DataSource[]data)
{
System.out.println("开始排序");
intarrayLength=data.length;
for(inti=1;i{
//当整体后移的时候,保证data[i]处的数据不会丢失
DataSourcetmp=data[i];
//i索引处的值已经比前面的所有值都大,表明已经有序,无需插入
//i-1索引前的数据已经是有序的,i-1索引处元素的值是最大值
if(data[i].compareTo(data[i-1])<0)
{
//整体后移一格
intj=i-1;
for(;j>=0&&data[j].compareTo(tmp)>0;j--)
{
data[j+1]=data[j];
}
data[j+1]=tmp;
}
System.out.println(Arrays.toString(data));
}
}
publicstaticvoidmain(String[]args)
{
DataSource[]data={newDataSource(9,""),newDataSource(-16,""),
newDataSource(21,"*"),newDataSource(23,""),
newDataSource(-30,""),newDataSource(-49,""),
newDataSource(21,""),newDataSource(30,"*"),
newDataSource(30,"")};
System.out.println("排序之前:
\n"+Arrays.toString(data));
insertSort(data);
System.out.println("排序之后:
\n"