您的位置:首页 > 编程语言

排序算法原理、图解、代码实现

2015-09-09 21:49 435 查看
转自:http://blog.csdn.net/ggxxkkll/article/details/8667429

一 插入排序

1.1 直接插入排序

基本思想:每次将一个待排序额记录按其关键码的大小插入到一个已经排好序的有序序列中,直到全部记录排好序。

图解:



代码实现:

[cpp] view
plaincopy

//直接顺序排序

void InsertSort(int r[], int n)

{

for (int i=2; i<n; i++)

{

r[0]=r[i]; //设置哨兵

for (int j=i-1; r[0]<r[j]; j--) //寻找插入位置

r[j+1]=r[j]; //记录后移

r[j+1]=r[0];

}

for(int k=1;k<n;k++)

cout<<r[k]<<" ";

cout<<"\n";

}

1.2 希尔排序

基本思想是: 先将整个待排序记录序列分割成若干个子序列,在在序列内分别进行直接插入排序,待整个序列基本有序时,再对全体记录进行一次直接插入排序。

图解:




代码实现:

[cpp] view
plaincopy

<span style="font-size:14px;">//希尔排序

void ShellSort(int r[], int n)

{

int i;

int d;

int j;

for (d=n/2; d>=1; d=d/2) //以增量为d进行直接插入排序

{

for (i=d+1; i<n; i++)

{

r[0]=r[i]; //暂存被插入记录

for (j=i-d; j>0 && r[0]<r[j]; j=j-d)

r[j+d]=r[j]; //记录后移d个位置

r[j+d]=r[0];

}

}

for(i=1;i<n;i++)

cout<<r[i]<<" ";

cout<<"\n";

}</span>

二 交换排序

2.1 起泡排序

起泡排序是交换排序中最简单的排序方法,其基本思想是: 两两比较相邻记录的关键码,如果反序则交换,直到没有反序的记录为止。

图解:



代码实现:

[cpp] view
plaincopy

<span style="font-size:14px;">//起泡排序

void BubbleSort(int r[], int n)

{

int temp;

int exchange;

int bound;

exchange=n-1; //第一趟起泡排序的范围是r[0]到r[n-1]

while (exchange) //仅当上一趟排序有记录交换才进行本趟排序

{

bound=exchange;

exchange=0;

for (int j=0; j<bound; j++) //一趟起泡排序

if (r[j]>r[j+1])

{

temp=r[j];

r[j]=r[j+1];

r[j+1]=temp;

exchange=j; //记录每一次发生记录交换的位置

}

}

for(int i=0;i<n;i++)

cout<<r[i]<<" ";

cout<<"\n";

}</span>



2.2快速排序

基本思想:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。

图解:







代码实现:

[cpp] view
plaincopy

//快速排序一次划分

int Partition(int r[], int first, int end)

{

int i=first; //初始化

int j=end;

int temp;

while (i<j)

{

while (i<j && r[i]<= r[j])

j--; //右侧扫描

if (i<j)

{

temp=r[i]; //将较小记录交换到前面

r[i]=r[j];

r[j]=temp;

i++;

}

while (i<j && r[i]<= r[j])

i++; //左侧扫描

if (i<j)

{

temp=r[j];

r[j]=r[i];

r[i]=temp; //将较大记录交换到后面

j--;

}

}

return i; //i为轴值记录的最终位置

}

//快速排序

void QuickSort(int r[], int first, int end)

{

if (first<end)

{ //递归结束

int pivot=Partition(r, first, end); //一次划分

QuickSort(r, first, pivot-1);//递归地对左侧子序列进行快速排序

QuickSort(r, pivot+1, end); //递归地对右侧子序列进行快速排序

}

}



三 选择排序

3.1 简单选择排序

基本思想:设所排序序列的记录个数为n。i取1,2,…,n-1,从所有n-i+1个记录(Ri,Ri+1,…,Rn)中找出排序码最小的记录,与第i个记录交换。执行n-1趟 后就完成了记录序列的排序。

图解:



代码实现:

[cpp] view
plaincopy

//简单选择排序

void SelectSort(int r[ ], int n)

{

int i;

int j;

int index;

int temp;

for (i=0; i<n-1; i++) //对n个记录进行n-1趟简单选择排序

{

index=i;

for (j=i+1; j<n; j++) //在无序区中选取最小记录

if (r[j]<r[index])

index=j;

if (index!=i)

{

temp=r[i];

r[i]=r[index];

r[index]=temp;

}

}

for(i=0;i<n;i++)

cout<<r[i]<<" ";

cout<<"\n";

}

3.2 堆排序

堆的定义

是具有下列性质的完全二叉树:每个结点的值都小于或等于其左右孩子结点的值(小根堆);或者每个结点的值都大于或等于其左右孩子结点的值(大根堆)。

大根堆和小根堆:根结点(亦称为堆顶)的关键字是堆里所有结点关键字中最小者的堆称为小根堆,又称最小堆。根结点(亦称为堆顶)的关键字是堆里所有结点关键字中最大者,称为大根堆,又称最大堆。注意:①堆中任一子树亦是堆。②以上讨论的堆实际上是二叉堆(Binary
Heap),类似地可定义k叉堆。





假设当前要筛选结点的编号为k,堆中最后一个结点的编号为m,并且结点k的左右子树均是堆(即r[k+1] ~ r[m]满足堆的条件),则筛选算法用伪代码可描述为:



具体的筛选代码如下:

[cpp] view
plaincopy

//筛选法调整堆

void Sift(int r[], int k, int m)

{

int i;

int j;

int temp;

i=k;

j=2*i+1; //置i为要筛的结点,j为i的左孩子

while (j<=m) //筛选还没有进行到叶子

{

if (j<m && r[j]<r[j+1])

j++; //比较i的左右孩子,j为较大者

if (r[i]>r[j]) break; //根结点已经大于左右孩子中的较大者

else

{

temp=r[i];

r[i]=r[j];

r[j]=temp; //将根结点与结点j交换

i=j;

j=2*i+1; //被筛结点位于原来结点j的位置

}

}

}

堆排序

堆排序的基本思想是:首先将待排序的记录序列构造成一个堆,此时,选出了堆中所有记录的最大者即堆顶记录,然后将它从堆中移走(通常将堆顶记录和堆中最后一个记录交换),并将剩余的记录再调整成堆,这样又找出了次大的记录,以此类推,直到堆中只有一个记录为止。

(1)用大根堆排序的基本思想
① 先将初始文件R[1..n]建成一个大根堆,此堆为初始的无序区
② 再将关键字最大的记录R[1](即堆顶)和无序区的最后一个记录R
交换,由此得到新的无序区R[1..n-1]和有序区R
,且满足R[1..n-1].keys≤R
.key
③由于交换后新的根R[1]可能违反堆性质,故应将当前无序区R[1..n-1]调整为堆。然后再次将R[1..n-1]中关键字最大的记录R[1]和该区间的最后一个记录R[n-1]交换,由此得到新的无序区R[1..n-2]和有序区R[n-1..n],且仍满足关系R[1..n-2].keys≤R[n-1..n].keys,同样要将R[1..n-2]调整为堆。
……

直到无序区只有一个元素为止。

(2)大根堆排序算法的基本操作:

① 初始化操作:将R[1..n]构造为初始堆;

② 每一趟排序的基本操作:将当前无序区的堆顶记录R[1]和该区间的最后一个记录交换,然后将新的无序区调整为堆(亦称重建堆)。

注意:

①只需做n-1趟排序,选出较大的n-1个关键字即可以使得文件递增有序。

②用小根堆排序与利用大根堆类似,只不过其排序结果是递减有序的。堆排序和直接选择排序相反:在任何时刻堆排序中无序区总是在有序区之前,且有序区是在原向量的尾部由后往前逐步扩大至整个向量为止











代码实现:

[cpp] view
plaincopy

//堆排序

void HeapSort(int r[ ], int n)

{

int i;

int temp;

for (i=n/2; i>=0; i--) //初始建堆,从最后一个非终端结点至根结点

Sift(r, i, n) ;

for (i=n-1; i>0; i--) //重复执行移走堆顶及重建堆的操作

{

temp=r[i];

r[i]=r[0];

r[0]=temp;

Sift(r, 0, i-1);

}

for(i=0;i<n;i++)

cout<<r[i]<<" ";

cout<<"\n";

}

四 归并排序

二路归并排序
基本思想:将若干个有序序列进行两两归并,直至所有待排序记录都在一个有序序列为止。



一路归并算法实现:

[cpp] view
plaincopy

//一次归并

void Merge(int r[], int r1[], int s, int m, int t)

{

int i=s;

int j=m+1;

int k=s;

while (i<=m && j<=t)

{

if (r[i]<=r[j])

r1[k++]=r[i++]; //取r[i]和r[j]中较小者放入r1[k]

else

r1[k++]=r[j++];

}

if (i<=m)

while (i<=m) //若第一个子序列没处理完,则进行收尾处理

r1[k++]=r[i++];

else

while (j<=t) //若第二个子序列没处理完,则进行收尾处理

r1[k++]=r[j++];

}



[cpp] view
plaincopy

//一趟归并

void MergePass(int r[ ], int r1[ ], int n, int h)

{

int i=0;

int k;

while (i<=n-2*h) //待归并记录至少有两个长度为h的子序列

{

Merge(r, r1, i, i+h-1, i+2*h-1);

i+=2*h;

}

if (i<n-h)

Merge(r, r1, i, i+h-1, n); //待归并序列中有一个长度小于h

else for (k=i; k<=n; k++) //待归并序列中只剩一个子序列

r1[k]=r[k];

}

//归并排序的非递归算法

void MergeSort1(int r[ ], int r1[ ], int n )

{

int h=1;

int i;

while (h<n)

{

MergePass(r, r1, n-1, h); //归并

h=2*h;

MergePass(r1, r, n-1, h);

h=2*h;

}

for(i=0;i<n;i++)

cout<<r[i]<<" ";

cout<<"\n";

}

下面看看二路归并排序的递归实现



[cpp] view
plaincopy

//归并排序的递归算法

void MergeSort2(int r[], int r1[], int r2[],int s, int t)

{

int m;

if (s==t)

{

r1[s]=r[s];

}

else

{

m=(s+t)/2;

MergeSort2(r, r2, r1, s, m); //归并排序前半个子序列

MergeSort2(r, r2, r1, m+1, t); //归并排序后半个子序列

Merge(r2, r1, s, m, t); //将两个已排序的子序列归并

}

}

总结

各种排序方法的比较(未完待续):



基数排序

经典排序算法 - 基数排序Radix sort

原理类似桶排序,这里总是需要10个桶,多次使用

首先以个位数的值进行装桶,即个位数为1则放入1号桶,为9则放入9号桶,暂时忽视十位数

例如

待排序数组[62,14,59,88,16]简单点五个数字

分配10个桶,桶编号为0-9,以个位数数字为桶编号依次入桶,变成下边这样

| 0 | 0 | 62 | 0 | 14 | 0 | 16 | 0 | 88 | 59 |

| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |桶编号

将桶里的数字顺序取出来,

输出结果:[62,14,16,88,59]

再次入桶,不过这次以十位数的数字为准,进入相应的桶,变成下边这样:

由于前边做了个位数的排序,所以当十位数相等时,个位数字是由小到大的顺序入桶的,就是说,入完桶还是有序

| 0 | 14,16 | 0 | 0 | 0 | 59 | 62 | 0 | 88 | 0 |

| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |桶编号

因为没有大过100的数字,没有百位数,所以到这排序完毕,顺序取出即可

最后输出结果:[14,16,59,62,88]


算法分析

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

空间复杂度:O(10n) (10表示0~9,用于存储临时的序列)

稳定性:稳定





算法实现

实现

[cpp] view
plaincopy

/********************************************************

*函数名称:GetNumInPos

*参数说明:num 一个整形数据

* pos 表示要获得的整形的第pos位数据

*说明: 找到num的从低到高的第pos位的数据

*********************************************************/

int GetNumInPos(int num,int pos)

{

int temp = 1;

for (int i = 0; i < pos - 1; i++)

temp *= 10;

return (num / temp) % 10;

}

/********************************************************

*函数名称:RadixSort

*参数说明:pDataArray 无序数组;

* iDataNum为无序数据个数

*说明: 基数排序

*********************************************************/

#define RADIX_10 10 //整形排序

#define KEYNUM_31 10 //关键字个数,这里为整形位数

void RadixSort(int* pDataArray, int iDataNum)

{

int *radixArrays[RADIX_10]; //分别为0~9的序列空间

for (int i = 0; i < 10; i++)

{

radixArrays[i] = (int *)malloc(sizeof(int) * (iDataNum + 1));

radixArrays[i][0] = 0; //index为0处记录这组数据的个数

}

for (int pos = 1; pos <= KEYNUM_31; pos++) //从个位开始到31位

{

for (int i = 0; i < iDataNum; i++) //分配过程

{

int num = GetNumInPos(pDataArray[i], pos);

int index = ++radixArrays[num][0];

radixArrays[num][index] = pDataArray[i];

}

for (int i = 0, j =0; i < RADIX_10; i++) //收集

{

for (int k = 1; k <= radixArrays[i][0]; k++)

pDataArray[j++] = radixArrays[i][k];

radixArrays[i][0] = 0; //复位

}

}

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: