【重新上本科】快速排序【上】

来源:互联网 发布:linux运行nodejs项目 编辑:程序博客网 时间:2024/04/27 17:19

去年,刚刚老同学庆祝本科毕业十周年。十年了,很多当年老师教的基础知识都忘记了。这几天捡起来再看看,复习一下,顺便写成博客。因为毕业后干的是码农的活儿,所以尽量用代码说话。

 

快速排序,在《算法导论》中是经典的分治算法,就是将问题分解成若干的子问题,子问题的解组成了整个问题的解。快速排序也是经典的递归算法的应用。

 

快速排序的函数原型通常定义如下:

 

void QuickSort (int Array[], intiLow, int iHigh)

 

快速排序的基本思想是:首先设定一个锚值(通常取序列的第一个元素),将数字序列分成两部分,一部分比锚值大,另一部分比锚值小;然后递归调用函数本身,递归对序列的这两部分进行求解。函数的框架很清晰,如下:

 

void QuickSort (int Array[], intiLow, int iHigh)

{

if (iLow < iHigh)

{

// 选取锚值

int iVal = Array[iLow];

 

// todo: 将数字序列分成两部分,一部分比锚值大,另一部分比锚值小

......

 

// 递归求解两个子问题

QuickSort (iLow, j-1);
QuickSort (j+1, iHigh);

}

}

算法的关键其实在“todo”部分,即,如何“将数字序列分成两部分,一部分比锚值大,另一部分比锚值小”。对于这个问题的处理,我参考的两本书上 有不同的思路,一本书是严蔚敏的《数据结构》,另外一本是《算法导论》。在直接看书之前,我个人还是习惯自己想想,能否自己写出来。

 

如何“将数字序列分成两部分,一部分比锚值大,另一部分比锚值小”?

 

分别从序列的两端对序列进行遍历:从序列的左端(起始端)向后遍历,寻找第一个元素,其值比锚值小;从序列的右端(终端)向前遍历,寻找第一个元素,其值比锚值大;然后,交换左右两端的元素;重复上述过程,直到两个遍历指示器相遇。代码如下:

int i = iLow+1, j = iHigh;
while (i < j)
{

// 从序列的左端(起始端)向后遍历,寻找第一个元素,其值比锚值小
while (i < j && Array[i] < iVal)

i++;

// 从序列的右端(终端)向前遍历,寻找第一个元素,其值比锚值大
while (i < j && Array[j] > iVal)

j--;

// 交换左右两端的元素;重复上述过程,直到两个遍历指示器相遇
if (i < j)
{

int iTemp = Array[i];
Array[i] = Array[j];
Array[j] = iTemp;
i++;
j--;

}

}

调试以上代码的时候,发现最终结果有错误。其问题根源在于如下问题:当两个遍历指示器相遇的时候,锚值元素应该放到哪儿?再具体一些,当两个指示器 相遇的时候,此时i的值等于j的值,那么Array[j]与锚值是什么关系?这个得分情况看,当相遇前遍历的方向是从前向后,即动的元素是i,则 Array[j]的值在上一轮比较中大于锚值,此时锚值正确的位置应该是j-1;否则,当相遇前遍历的方向是从右向左,则Array[j]的值小于锚值, 锚值正确的位置是j。总而言之,以上的代码,Array[j]的值可以大于也可以小于锚值,造成锚值最后的位置不确定,那么要加入代码来判断上述情况,并根据其位置进行递归调用。代码如下:

if (Array[j] < iVal )  // i和j相遇前最后一次遍历是从右向左,锚值的正确位置是j
{

// 锚值目前在Array[iLow],将它放到Array[j],并将Array[j]的值(比锚值小)放到锚值前面

Array[iLow] = Array[j];

Array[j] = iVal;

// 锚值的位置在j,两个数字子序列是[iLow,j-1]和[j+1, iHigh],递归解决

QuickSort (iLow, j-1);
QuickSort (j+1, iHigh);

}
else                // i和j相遇前最后一次遍历是从左向右,锚值的正确位置是j-1
{

// 锚值目前在Array[iLow],将它放到Array[j-1],并将Array[j-1]的值(比锚值小,Array[j]的值比锚值大)放到锚值前面 

Array[iLow] = Array[j-1];

Array[j-1] = iVal;

// 锚值的位置在j-1,两个数字子序列是[iLow,j-2]和[j, iHigh],递归解决

QuickSort (iLow, j-2);
QuickSort (j, iHigh);

 }

方便起见,将上述代码合并成完整代码,如下:

void QuickSort (int Array[], intiLow, int iHigh)

{

if (iLow < iHigh)

{

// 选取锚值

int iVal = Array[iLow];

 

// 将数字序列分成两部分,一部分比锚值大,另一部分比锚值小

int i = iLow+1, j = iHigh;
while (i < j)
{

// 从序列的左端(起始端)向后遍历,寻找第一个元素,其值比锚值小
while (i < j && Array[i] < iVal)

i++;

// 从序列的右端(终端)向前遍历,寻找第一个元素,其值比锚值大
while (i < j && Array[j] > iVal)

j--;

// 交换左右两端的元素;重复上述过程,直到两个遍历指示器相遇
if (i < j)
{

int iTemp = Array[i];
Array[i] = Array[j];
Array[j] = iTemp;
i++;
j--;

}

} // while

 

// 递归求解两个子问题

if (Array[j] < iVal )  // i和j相遇前最后一次遍历是从右向左,锚值的正确位置是j
{

// 锚值目前在Array[iLow],将它放到Array[j],并将Array[j]的值(比锚值小)放到锚值前面

Array[iLow] = Array[j];

Array[j] = iVal;

// 锚值的位置在j,两个数字子序列是[iLow,j-1]和[j+1, iHigh],递归解决

QuickSort (iLow, j-1);
QuickSort (j+1, iHigh);

}
else                // i和j相遇前最后一次遍历是从左向右,锚值的正确位置是j-1
{

// 锚值目前在Array[iLow],将它放到Array[j-1],并将Array[j-1]的值(比锚值小,Array[j]的值比锚值大)放到锚值前面 

Array[iLow] = Array[j-1];

Array[j-1] = iVal;

// 锚值的位置在j-1,两个数字子序列是[iLow,j-2]和[j, iHigh],递归解决

QuickSort (iLow, j-2);
QuickSort (j, iHigh);

 } // if

} // if

}

见过这么麻烦的快速排序么?见过这么啰嗦的代码么?虽然十年过去了,不过记得教科书上的代码没有这么多。不过,经过测试,这部分代码真的能够正确work!说明我理解算法的思路是正确的,在关键问题上(锚值位置的处理)走了弯路。下一篇,看看弯路在哪里,更正过来。

 

to be continued......

 

 

 


原创粉丝点击