数据结构.docx
《数据结构.docx》由会员分享,可在线阅读,更多相关《数据结构.docx(70页珍藏版)》请在冰豆网上搜索。
数据结构
**********************************************************************
1、排序
**********************************************************************
内排序与外排序:
内排序是指在排序期间数据对象全部存放在内存的排序;外排序是指在排序期间全部对象个数太多,不能同时存放在内存,必须根据排序过程的要求,不断在内、外存之间移动的排序。
################################################
快速排序(QuickSort)
快速排序方法的基本思想:
是任取待排序对象序列中的某个对象(例如取第一个对象)作为基准,按照该对象的关键码大小,将整个对象序列划分为左右两个子序列:
左侧子序列中所有对象的关键码都小于或等于基准对象的关键码;
右侧子序列中所有对象的关键码都大于基准对象的关键码;
基准对象则排在这两个子序列中间(这也是该对象最终应安放的位置)。
然后分别对这两个子序列重复施行上述方法,直到所有的对象都排在相应位置上为止。
是一种不稳定的排序方法。
快速排序算法的基本特性:
时间复杂度:
O(n*lgn)
最坏:
O(n^2)
空间复杂度:
O(n*lgn)
快速排序算法的描述:
快速排序时基于分治模式处理的,对一个典型子数组A[p...r]排序的分治过程为三个步骤:
1.分解:
A[p..r]被划分为俩个(可能空)的子数组A[p..q-1]和A[q+1..r],使得
A[p..q-1]<=A[q]<=A[q+1..r]
2.解决:
通过递归调用快速排序,对子数组A[p..q-1]和A[q+1..r]排序。
3.合并。
intpartition(intdata[],intlo,inthi)//引自whatever。
{
intkey=data[lo];
intl=lo;
inth=hi;
while(l{
while(key<=data[h]&&ldata[l]=data[h];
while(data[l]<=key&&ldata[h]=data[l];
}//如此,小的在前,大的在后,一分为二
data[l]=key;
returnl;
}
*******************************
intpartition(intdata[],intlo,inthi)
{
intkey=data[hi];//以最后一个元素,data[hi]为主元
inti=lo-1;
for(intj=lo;j{
if(data[j]<=key)
{
i=i+1;
swap(&data[i],&data[j]);
}
}
swap(&data[i+1],&data[hi]);//不能改为swap(&data[i+1],&key)
returni+1;
}
**********************************
intpartition(intdata[],intlo,inthi)//请读者思考
{
intkey=data[hi];//以最后一个元素,data[hi]为主元
inti=lo-1;
for(intj=lo;j<=hi;j++)//现在,我让j从lo指向了hi,不是hi-1。
{
if(data[j]<=key)
{
i=i+1;
swap(&data[i],&data[j]);
}
}
//swap(&data[i+1],&data[hi]);//去掉这行
returni;//返回i,非i+1.
}
//////////////////////////////////////////////////
voidswap(int*a,int*b)
{
inttemp=*a;
*a=*b;
*b=temp;
}
//然后是,调用partition,对整个数组进行递归排序:
voidQuickSort(intdata[],intlo,inthi)
{
if(lo{
intk=partition(data,lo,hi);
QuickSort(data,lo,k-1);
QuickSort(data,k+1,hi);
}
}
################################################
插入排序(InsertSorting)
插入排序的基本方法是:
每步将一个待排序的对象,按其关键码大小,插入到前面已经排好序的一组对象的适当位置上,直到对象全部插入为止。
直接插入排序的基本思想是:
当插入第i(i>=1)个对象时,前面的v[0],v[1],…,v[i-1]已经排好序。
这时,用v[i]的关键码与v[i-1],v[i-2],…的关键码顺序进行比较,找到插入位置即将v[i]插入,原来位置上的对象向后顺移。
时间复杂度为O(n^2)。
是一种稳定的排序方法。
voidInsertSort(KeyTypear[],intn)
{
for(inti=2;i<=n;++i)
{
if(ar[i]{
ar[0]=ar[i];
intj=i-1;
do
{
ar[j+1]=ar[j];
--j;
}while(ar[0]ar[j+1]=ar[0];
}
}
}
折半插入排序基本思想是:
设在顺序表中有一个对象序列v[0],v[1],…,v[n-1]。
其中,v[0],v[1],…,v[i-1]是已经排好序的对象。
在插入v[i]时,利用折半搜索法寻找v[i]的插入位置。
是一个稳定的排序方法。
voidBinaryInsertSort(KeyTypear[],intn)
{
for(inti=2;i<=n;++i)
{
if(ar[i]{
ar[0]=ar[i];
intleft=1,right=i-1;
while(left<=right)//
{
intmid=(right-left)/2+left;
if(ar[0]right=mid-1;
else
left=mid+1;
}
for(intj=i;j>=left;--j)
{
ar[j]=ar[j-1];
}
ar[left]=ar[0];
}
}
}
################################################
希尔排序(ShellSort)
该方法的基本思想是:
设待排序对象序列有n个对象,首先取一个整数gap然后缩小间隔gap,例如取gap=gap/2,重复上述的子序列划分和排序工作。
直到最后取gap==1,将所有对象放在同一个序列中排序为止。
是一个不稳定的排序方法;平均时间复杂度:
O(
)
voidShellInsert(KeyTypear[],intn,intgap)
{
for(inti=1+gap;i<=n;++i)
{
if(ar[i]{
ar[0]=ar[i];
intj=i-gap;
do
{
ar[j+gap]=ar[j];
j-=gap;
}while(j>0&&ar[0]ar[j+gap]=ar[0];
}
}
}
voidShellSort(KeyTypear[],intn)
{
intgap=n/3;
while(gap>=1)
{
ShellInsert(ar,n,gap);
gap=gap==2?
1:
(int)(gap/3);
}
}
################################################
起泡排序(BubbleSort)
起泡排序的基本方法是:
设待排序对象序列中的对象个数为n。
最多作n-1趟,i=1,2,...,n-2。
在第i趟中顺次两两比较v[n-j-1].Key和v[n-j].Key,j=n-1,n-2,...,i。
如果发生逆序,则交换v[n-j-1]和v[n-j]。
voidSwap(KeyType&a,KeyType&b)
{
KeyTypex=a;
a=b;
b=x;
}
voidBubbleSort(KeyTypear[],intn)
{
for(inti=1;i{
inttag=1;
intj;
for(j=1;j<=n-i;++j)
{
if(ar[j]>ar[j+1])
{
tag=0;
Swap(ar[j],ar[j+1]);
}
}
if(tag==1)break;
tag=1;
for(j=n-i;j>i;--j)
{
if(ar[j]{
tag=0;
Swap(ar[j],ar[j-1]);
}
}
if(tag==1)break;
}
}
################################################
交换排序(ExchangeSort)
交换排序的基本思想:
是两两比较待排序对象的关键码,如果发生逆序(即排列顺序与排序后的次序正好相反),则交换之,直到所有对象都排好序为止。
################################################
选择排序
选择排序的基本思想是:
每一趟(例如第i趟,i=0,1,…,n-2)在后面n-i个待排序对象中选出关键码最小的对象,作为有序对象序列的第i个对象。
待到第n-2趟作完,待排序对象只剩下1个,就不用再选了。
voidSelectSort(KeyTypear[],intn)
{
for(inti=1;i{
intk=i;
for(intj=i+1;j<=n;++j)
{
if(ar[k]>ar[j])
{
k=j;
}
}
if(k!
=i)
{
Swap(ar[k],ar[i]);
}
}
}
################################################
锦标赛排序(TournamentTreeSort)
它的思想与体育比赛时的淘汰赛类似。
首先取得n个对象的关键码,进行两两比较,得到n/2个比较的优胜者(关键码小者),作为第一步比较的结果保留下来。
然后对这n/2个对象再进行关键码的两两比较,…,如此重复,直到选出一个关键码最小的对象为止。
structDataNode
{
KeyTypedata;//数据值
intindex;//结点在满二叉树中顺序号
intactive;//参选标志:
=1,参选,=0,不参选
};
intPowerOfTwo(intn)
{
inti=1;
while(i{
i<<=1;//00000000000000000000000000010000
}
returni;
}
voidUpdateTree(DataNode*tree,intindex)
{
inti=0,j;
if(index%2==0)i=index+1;
elsei=index-1;
tree[i/2]=tree[i];
i=i/2;
while(i>1)
{
if(i%2==0)j=i+1;
elsej=i-1;
if(tree[i].active==1&&tree[j].active==1)
{
if(tree[i].data{
tree[i/2]=tree[i];
}
else
{
tree[i/2]=tree[j];
}
}
else
{
if(tree[i].active==1)
tree[i/2]=tree[i];
else
tree[i/2]=tree[j];
}
i=i/2;
}
}
voidTournamentSort(KeyTypear[],intn)//7
{
DataNode*tree;
DataNodeitem;
intbottomRowSize=PowerOfTwo(n);//8
intTreeSize=2*bottomRowSize;//16
intloadindex=bottomRowSize;
tree=newDataNode[TreeSize];
inti,j;
for(i=loadindex,j=1;i{
if(j<=n)
{
tree[i].data=ar[j];
tree[i].active=1;
}else
{
tree[i].active=0;
}
tree[i].index=i;
}
i=loadindex;
while(i>1)
{
j=i;
while(j<2*i)
{
if(tree[j+1].active==0||tree[j].datatree[j/2]=tree[j];
else
tree[j/2]=tree[j+1];
j+=2;
}
i=i/2;
}
for(i=1;i?
{
ar[i]=tree[1].data;
tree[tree[1].index].active=0;
UpdateTree(tree,tree[1].index);
}
ar[i]=tree[1].data;
delete[]tree;
}
################################################
堆排序(HeapSort)
利用堆及其运算,可以很容易地实现选择排序的思路。
堆排序分为两个步骤:
第一步,根据初始输入数据,利用堆的调整算法FilterDown()形成初始堆,第二步,通过一系列的对象交换和重新调整堆进行排序。
基于初始堆进行堆排序:
最大堆的第一个对象V[0]具有最大的关键码,将V[0]与V[n]对调,把具有最大关键码的对象交换到最后,再对前面的n-1个对象,使用堆的调整算法FilterDown(0,n-1),重新建立最大堆。
结果具有次最大关键码的对象又上浮到堆顶,即V[0]位置。
再对调V[0]和V[n-1],调用FilterDown(0,n-2),对前n-2个对象重新调整,…。
如此反复执行,最后得到全部排序好的对象序列。
这个算法即堆排序算法,其细节在下面的程序中给出。
时间复杂性为O(nlog2n)。
是一个不稳定的排序方法。
voidFilterDown(KeyTypear[],constintstart,constintHeadOfEnd)
{
inti=start,j=2*i;
ar[0]=ar[i];
while(j<=HeadOfEnd)
{
if(jar[j])++j;
if(ar[0]>=ar[j])break;
ar[i]=ar[j];
i=j;
j=2*i;
}
ar[i]=ar[0];
}
voidHeapSort(KeyTypear[],intn)
{
intpos=n/2;
while(pos>0)
{
FilterDown(ar,pos,n);
--pos;
}
inti=n;
while(i>1)
{
Swap(ar[1],ar[i]);
--i;
FilterDown(ar,1,i);
}
}
################################################
归并排序(MergeSort)
归并,是将两个或两个以上的有序表合并成一个新的有序表。
对象序列initList中有两个有序表V[l]…V[m]和V[m+1]…V[n]。
它们可归并成一个有序表,存于另一对象序列mergedList的V[l]…V[n]中。
这种归并方法称为两路归并(2-waymerging)。
其基本思想是:
设两个有序表A和B的对象个数(表长)分别为al和bl,变量i和j分别是表A和表B的当前检测指针。
设表C是归并后的新有序表,变量k是它的当前存放指针。
当i和j都在两个表的表长内变化时,根据A[i]与B[j]的关键码的大小,依次把关键码小的对象排放到新表C[k]中;
当i与j中有一个已经超出表长时,将另一个表中的剩余部分照抄到新表C[k]中。
时间复杂度为O(nlog2n)。
是一个稳定的排序方法。
归并排序占用附加存储较多,需要另外一个与原待排序对象数组同样大小的辅助数组。
这是这个算法的缺点。
voidMerge(KeyTypear[],KeyTypebr[],constintleft,constintm,constintright)
{
inti=left,j=m+1;
intk=left;
while(i<=m&&j<=right)
{
br[k++]=ar[i]ar[i++]:
ar[j++];
}
while(i<=m)
{
br[k++]=ar[i++];
}
while(j<=right)
{
br[k++]=ar[j++];
}
for(k=left,i=left;i<=right;++k,++i)
{
ar[i]=br[k];
}
}
voidMergeSort(KeyTypear[],KeyTypebr[],constintleft,constintright)
{
if(left{
intmid=(right-left)/2+left;
MergeSort(ar,br,left,mid);
MergeSort(ar,br,mid+1,right);
Merge(ar,br,left,mid,right);
}
}
voidMergeSort(KeyTypear[],intn)
{
KeyType*br=newKeyType[n+1];
MergeSort(ar,br,1,n);
delete[]br;
}
################################################
基数排序(RadixSort)
基数排序是采用“分配”与“收集”的办法,用对多关键码进行排序的思想实现对单关键码进行排序的方法
//基数排序(RadixSort)
#defineNUM10
voidRadixSort(KeyTypeArray[],intn,intD)
{
inti,j,k,l=1,d=0;
//分配中间存储空间
int**ppArr=newint*[NUM];
for(i=0;i{
ppArr[i]=newint[n];
}
intpNum[NUM];
//分趟分配收集
while(d{
for(i=0;i{
pNum[i]=-1;
}
for(i=0;i{
j=(Array[i]/l)%NUM;
k=++pNum[j];
ppArr[j][k]=Array[i];
}
for(k=0,i=0;i{
for(j=0;j<=pNum[i];j++)
{
Array[k++]=ppArr[i][j];
}
}
d++;
l*=10;
}
}
voidRadixSort(KeyTypear[],intn)
{
RadixSort(ar,n,0);
}
不稳定排序:
快速排序;堆排序;简单选择排序。
排序方法
比较次数
移动次数
稳定性
附加存储
最好
最差
最好
最差
最好
最差
直接插入排序
n
n2
0
n2
1
折半插入排序
nlog2n
0
n2
1
起泡排序
n
n2
0
n2
1
快速排序
nlog2n
n2
nlog2n
n2
log2n
n2
简单选择排序
n2
0
n
1
锦标赛排序
nlog2n
nlog2n
n
堆排序
nlog2n
nlog2n
1
归并排序
nlog2n
nlog2n
n
希尔排序
n2
************************