现实生活中常有找“最大”、“最小”及“中位数”等需求,解决这样的问题不用将整个序列排序。寻找“最大”、“最小”问题可以用“堆”来完成( 时间复杂度不会超过O(logn) )。对于寻找“中位数”问题,可以将其抽象为寻找序列n中第k小元素的问题。《编程珠玑》中完成寻找序列n第k小元素问题的“划分序列”思路跟“快速排序”中“划分序列”思想结构相同。完成寻找第k小元素问题分两个层次来完成,1划分序列得第m小元素;2选择序列得k小元素。

1 Hoare选择算法实现

(1) 划分

[1] Lomuto划分

Lomuto方法根据值t将序列A[L, U]划分为小于t和大于等于t的两部分。用m指向小于t部分序列的最大下标,用i扫描还未经扫描的序列,如果遇见比t小的元素就将i指向的元素跟++m指向的元素交换,使小于t部分序列向右扩展;如果i指向的元素不小于t则不做任何操作;然后i增1直到i >  U。扫描k步后序列A[L…U]可能具有如下结构

当以t值将整个序列划分完毕时,A[L…U]这个序列被划分为如下结构

此时整个序列被分为两部分,小于t的部分及大于等于t的部分。此时m所指向的元素就是整个序列中第(m – L + 1)小的元素。此时A[L…m-1] < A[m] <= A[M+1…U]。

[2] Bob  Sedgewick划分

BobSedgewick从右往左扫描A[L…U]序列,用m指向大于或等于t序列的最左一个元素。用i从右往左扫描还未扫描的元素,如果i指向的元素比t大则跟--m所指向的元素交换,使大于t序列向左扩展;如果i所指元素比t小则不作任何处理;然后i减1直到i = L。经扫描k步之后序列A[L…U]可能具有如下结构

当以t值将整个序列划分完毕时,将A[L…U]这个序列被划分为如下结构

此时整个序列被分为两部分,小于t的部分及大于等于t的部分。此时m所指向的元素就是整个序列中第(m – L + 1)小的元素。此时A[L…m-1] < A[m] <= A[M+1…U]。

(2) Hoare选择第k元素程序实现

从AWK代码表述的算法还原而来,AWK函数易于被AWK程序测试。

需要用到的两元素交换函数Swap()和返回[L,U]间的随机函数RandInt()代码如下

typedef int TYPE;//产生一个随机数m, l <= m < h
int  RandInt(int l, int h)
{if( !(h - l) )return l;return  l + rand() % (h-l) ;
}//交换TYPE类型数组中下标为i和j两个元素的值
void  swap(TYPE *a, int i, int j)
{TYPE t;t   = a[i];a[i]    = a[j];a[j]    = t;
}

[1] Bob Sedgewick递归

int SedgewickRecursion(TYPE *a, int l, int u, int k)
{int i, m, r;TYPE t;if(l <=u ){//Both m and i point to the element which is next to the end onem = i = u + 1;r   = RandInt(l, u + 1);swap(l, r);//Flag the first elementt  = a[l];do{//Go on when meet the element which is smaller than t//Until to the first elementwhile(a[--i] < t);//Swap the element which is not smaller than t with --mswap(--m, i);}while(i != l);if(m < k - 1){//The k samll element in a[m + 1, u]SedgewickRecursion(m + 1, u, k);}else if(m > k - 1){//The k small element in a[l, m -1]SedgewickRecursion(l, m - 1, k);}}return a[m];
}

[2] Bob Sedgewick迭代

int SedgewickIteration(TYPE *a, int l, int u, int k)
{int i, m, r;TYPE t;while(l <=u ){//Both m and i point to the element which is next to the end onem = i = u + 1;r    = RandInt(l, u + 1);swap(l, r);//Flag the first elementt  = a[l];do{//Go on when meet the element which is smaller than t//Until to the first elementwhile(a[--i] < t);//Swap the element which is not smaller than t with --mswap(--m, i);}while(i != l);if(m < k - 1){//The k samll element in a[m + 1, u]l    = m + 1;}else if(m > k - 1){//The k small element in a[l, m -1]u   = m -1;}else {return a[m];}}
}

(3) 体会

Lomuto和Sedgewick划分其实是得到了A[L, m - 1] < t <= A[m, U]即确定t( a[L],首元素)是整个序列A的第(m – L + 1)小元素。有了随机函数RandInt()之后t的值就是整个序列中随机的一个元素,这样的随机性避免序列A的特殊性。如果需要寻找的k值比m小,则第k元素必定在A[L, m - 1]中;否则第k小元素在A[m +1, U]中,每运行一次划分代码,序列至少会排除一个元素而被丢掉所以最终一定能够找到第k小元素,最坏的是最后只剩下第k小元素。

(4) 测试

用AWK语言搭建一个脚手架测试所编写的程序。

[1] 编写AWK程序

在Linux终端中新建一个文件用来编写AWK程序:vi TestAlgo.awk,建立好后保存此文件。根据AWK程序框架编写AWK程序。程序中定义的函数除了语法与C稍有不同之外,其实现过程跟C程序都是一样的。所以,有关于C语言程序的算法都可以以AWK程序格式定义到AWK程序中,算法经测试后再用C将算法表达出来。

[2] 运行AWK程序

AWK程序可以直接在命令行终端编写,也可以将AWK程序编写在如上建立的TestAlgo.awk文件中。AWK程序的输入数据来自具体的文件或者用户输入stdin。

命令行终端运行AWK代码格式为

awk  ‘awk-code’ file 或者 awk ‘awk-code’

lly7@debian:~/AWK$ awk '$1==1 {print $2}' awk.dat

happy

lly7@debian:~/AWK$ awk '$1==1 {print $2}'

1 happy

happy

awk.dat文件中有一行的内容为” 1 happy”。当不用具体文件作为awk程序输入时,运行awk程序后就会等待用户的输入,当用户输入的第一个字段为1且第二个字段为happy时awk程序就会输出第二字段内容happy。

运行AWK程序文件格式为

awk -f  ‘awk-file’  file 或者 awk  -f  ‘awk-file’

如以下为TestAlgo.awk程序用文件作为输入时测试产生[L, U]整数的随机函数的界面,

lly7@debian:~/AWK$ awk -f TestAlgo.awk awk.dat

AWK Application Start

0.429662 0.3236170.026259 0.339192 0.793282

2 3 4 1 1 3 0 2 3 1

AWK Application END

lly7@debian:~/AWK$

用stdin流作为awk程序输入,测试产生[L, U]整数的随机函数的界面

lly7@debian:~/AWK$ awk -f TestAlgo.awk

AWK Application Start

fill 5

0.585005 0.1711470.462567 0.370760 0.368752

randint 10

0 4 3 1 2 2 3 1 4 0

^C

lly7@debian:~/AWK$

2 运行时间

分析Bob Sedgewick划分下实现的Hoare选择算法的时间复杂度。

(1) 数学角度分析

BobSedgewick划分比Lomuto划分法少一个交换语句且Bob Sedgewick以第一个元素为哨兵较少了循环语句内的判断语句。若整个程序大部分运行的时间在Hoare选择算法程序上,那么Bob Sedgewick算法就显得有优势。从另一个角度上来说,如果在众多的函数中都能够做到以上两点的优化,那么对于整个程序来说也是意义重大。

Hoare选择算法的递归和迭代的时间复杂度都是O(N)(递归后只选择2个序列的一个进行下一次操作),它运行的最坏情况是每运行一次只减少一个元素且最后才找到第k小元素。那么此时Bob Sedgewick划分法处理的元素个数为:
Hoare选择算法至少会处理N个元素,但就平均来讲,Hoare选择算法在处理完N个元素(Sedgewick划分)之后还有可能只处理1,2,3,…,N-1个元素,所以Hoare处理元素的平均个数为:

所以被Hoare选择算法划分的元素的总数在[N, N(N+1)/2]之间。

(2) grap图形分析

为了将数据用图形来描述,Jon  L. Bentley和Brian  W. Kernighan共同创建了Grap语言。在google里单独输入Grap几乎还没有它的消息。关于grap语言见” DebianGNU/Linux Desktop配置grap”笔记。

.G1
L = 1;
U = 100;
K =  int( (L + U) / 2 );
Y = (U - L + 1) * (L + U) / 2;
MUL = Y;
COUNT = 1;
define RandInt { ($1) + int( ( ($2) - ($1) ) * rand() )}I = 134;
frame invis
ticks left in from 0 to 0
ticks bot in from 0 to 0
label bot "Hoare find the k small number"line from K,0 to K,Yfor l from L to U by 1 do{if L <= U then {Y  = Y - int(MUL / 10);srand(I);I = I + 1;M = RandInt(L, U);COUNT = COUNT + U - L + 1;line from L, Y to U, Ybullet at M,Y;if K <= M then {U = M - 1};if K >= M then {L = M + 1};}
}
print COUNT.G2

修改随机数I及序列U的值后,在Linux命令行终端运行此grap程序:grap k_small.g | pic | groff  > k_small.
下面是当寻找[1, 10],[1, 100]的中位数过程图示,每组有两张图片,是在不同的随机数种子之下的实验。图中的黑点表示随机到的元素,等效于t,线条的长度形象的表示了当前中位数所在的集合。ComCount表示Hoare选择算法寻找中位数过程中元素比较次数。

      

不同随机数种子下寻找[1,10]中位数的过程

    

不同随机数种子寻找[1, 100]中位数的过程

3 附程序源码

(1) Lomuto 划分

[1] 递归

int LomutoRecursion(TYPE *a, int l, int u, int k)
{int i, m, r;TYPE t;if(l <=u ){m    = l;//Rand number r is between l and u + 1r   = RandInt(l, u + 1);//Save the firt element in tt = a[l];//Lomuto Dividefor(i = l + 1; i <= u; ++i){if(a[i] < t)swap(++m, i);}//Exchange the t and a[m]swap(l, m);if(m < k - 1){//The k samll element in a[m + 1, u]LomutoRecursion(m + 1, u, k);}else if(m > k - 1){//The k small element in a[l, m -1]LomutoRecursion(l, m - 1, k);}}return a[m];
}

[2] 迭代

int LomutoIteration(TYPE *a, int l, int u, int k)
{int i, m, r;TYPE t;while( l <= u){m    = l;//Rand number r is between l and u + 1r   = RandInt(l, u + 1);//Save first element in tt    = a[l];Lomuto Dividefor(i = l + 1; i <= u; ++i){if(a[i] < t)swap(++m, i);}//Exchange the t and a[m]swap(l, m);if(m < k - 1){//The k samll element in a[m + 1, u]l = m + 1;}else if(m > k - 1){//The k small element in a[l, m -1]u   = m -1;}else {return a[m];}}
}

(2) AWK脚手架代码

BEGIN{print "AWK Application Start"MULTI   = 10000
}function RandInt(l, h)
{if( !(h - l) )return l;return l + int(rand() * MULTI) % (h-l) ;
}function swap(i, j, t)
{t  = a[i];a[i]    = a[j];a[j]    = t;
}function SedgewickRecursion(l, u, k, i, m, t, r)
{if(l <=u ){#Both m and i point to the element which is next to the end onem = i = u + 1;r   = RandInt(l, u + 1);
print "r: " rswap(l, r);#Flag the first elementt  = a[l];do{#Go on when meet the element which is smaller than t#Until to the first elementwhile(a[--i] < t);#Swap the element which is not smaller than t with --mswap(--m, i);}while(i != l);
print "m: " m     if(m < k - 1){#The k samll element in a[m + 1, u]SedgewickRecursion(m + 1, u, k);}else if(m > k - 1){#The k small element in a[l, m -1]SedgewickRecursion(l, m - 1, k);}}
}function SedgewickIteration(l, u, k, i, m, t, r)
{while(l <=u ){#Both m and i point to the element which is next to the end onem = i = u + 1;r    = RandInt(l, u + 1);
print "r: " rswap(l, r);#Flag the first elementt  = a[l];do{#Go on when meet the element which is smaller than t#Until to the first elementwhile(a[--i] < t);#Swap the element which is not smaller than t with --mswap(--m, i);}while(i != l);
print "m: " m     if(m < k - 1){#The k samll element in a[m + 1, u]l  = m + 1;}else if(m > k - 1){#The k small element in a[l, m -1]u    = m -1;}else {return a[m];}}
}function LomutoRecursion(l, u, k, i, m, t, r)
{if(l <=u ){m   = l;r  = RandInt(l, u + 1);
print "r: " rt    = a[l];for(i = l + 1; i <= u; ++i){if(a[i] < t)swap(++m, i);}#Exchange the t and a[m]swap(l, m);
print "m: " m     if(m < k - 1){#The k samll element in a[m + 1, u]LomutoRecursion(m + 1, u, k);}else if(m > k - 1){#The k small element in a[l, m -1]LomutoRecursion(l, m - 1, k);}}
}function LomutoIteration(l, u, k, i, m, t, r)
{while( l <= u){m   = l;r  = RandInt(l, u + 1);
print "r: " rt    = a[l];for(i = l + 1; i <= u; ++i){if(a[i] < t)swap(++m, i);}#Exchange the t and a[m]swap(l, m);
print "m: " m     if(m < k - 1){#The k samll element in a[m + 1, u]l  = m + 1;}else if(m > k - 1){#The k small element in a[l, m -1]u    = m -1;}else {return a[m];}}
}$1=="fill" {n = $2;for(j = 0; j < n; ++j) a[j] = rand();printf("fill: ");for(j = 0; j < n; ++j) printf("%f ", a[j]);print "\n"
}$1=="randint"{for(j = 0; j < $2; ++j) printf("%d ",RandInt(0, n));print " "
}$1=="swap" {r1 = RandInt(0, n);r2 = RandInt(0, n);printf("r1: %d, r2: %d\n", r1, r2);swap(r1, r2);for(j = 0; j < n; ++j) printf("%f ", a[j]);print "\n"
}$1=="m" {a[0]=$2;a[1]=$3;
}$1=="sdrec" {SedgewickRecursion(0, n - 1, 2);printf("sdrec: ");for(j = 0; j < n; ++j) printf("%f ", a[j]);print " "
}$1=="sdite" {SedgewickIteration(0, n - 1, 5);printf("sdite: ");for(j = 0; j < n; ++j) printf("%f ", a[j]);print " "
}$1=="ltrec" {LomutoRecursion(0, n - 1, 1);printf("ltrec: ");for(j = 0; j < n; ++j) printf("%f ", a[j]);print " "
}$1=="ltite" {LomutoIteration(0, n - 1, 3);printf("ltite: ");for(j = 0; j < n; ++j) printf("%f ", a[j]);print " "
}END{print "AWK Application END"
}

Box Note Over.

Hoare选择算法 寻找第k小元素C实现 算法的“AWK脚手架和grap运行过程分析”相关推荐

  1. 算法-寻找第k小元素(C)

    序言 刚开始我认为,寻找第k小的元素:简单呀,先对所有元素排序,之后再找不就完事啦,这时时间复杂度在O(nlgn).那有没有更好的排序的方法了呢?答案:当然是有的. 算法基本思路: (1) 当规模小于 ...

  2. 解决寻找第K小元素问题——三种不同的算法实现

    个人原创,禁止转载--Zetrue_Li 问题描述:在一个序列里找出第K小元素 以下程序基于函数 int select_kth_smallest(list q, int k) 实现 :返回向量q中第k ...

  3. 寻找第K大元素的八大算法、源码及拓展

    寻找第K大元素的八大算法.源码及拓展 http://www.cnblogs.com/bethunebtj/p/4861378.html 一.问题描述 所谓"第(前)k大数问题"指的 ...

  4. 分治法实验-寻找第k小元素

    问题描述 随机生成含有n个不同元素的数组L(n≥10000),要求找出第k小的元素(k≤n),完成下面的任务: (1)设计一个基于排序选择算法程序,编程调试正确(排序算法自己确定). (2)设计一个时 ...

  5. [LeetCode题解]从两个有序数组的并集中寻找第k小元素

    Given two sorted arrays A, B of size m and n respectively. Find the k-th smallest element in the uni ...

  6. 分治算法 求第k小元素 O(n) O(nlog2^n)

    BFPRT算法:时间复杂度O(n)求第k小的数字(分治算法+快排) 各位小伙伴,由于本篇文章代码太过杂乱.我于 2018年12月25日 对文中介绍的算法进行了重写.点击上面的蓝色字体,可以阅读重写后的 ...

  7. C语言寻找第k小元素,小技巧——查找第k小的元素

    今天分享一个小技巧,虽然是小技巧但是还是很有价值的,曾经是微软的面试题.题目是这样的,一个无序的数组让你找出第k小的元素,我当时看到这道题的时候也像很多人一样都是按普通的思维,先排序在去第K个,但是当 ...

  8. 寻找中项和第k小元素c语言,分治法第k小元素poj2104.ppt

    分治法第k小元素poj2104 第六章 分 治 6.1 引言 分治法的设计思想是,将一个难以直接解决的大问题,分割成一些规模较小的相同问题,以便各个击破,分而治之. 战略 算法设计技术 划分--治理- ...

  9. 分治-寻找第k小的数

    给定线性序集中n个元素和一个整数k,1≤k≤n,要求找出这n个元素中第k小的元素 template<class Type> Type RandomizedSelect(Type a[],i ...

最新文章

  1. Python之文件操作
  2. 华三交换机配置多个镜像口_配置本地端口镜像详解(多个镜像口:多个观察口)...
  3. 微信群「斗图」总输,Python助我超神!
  4. schema约束文档与xml文件详解
  5. java异常的总接口_重构:Java特别的接口修改:在throws子句中添加一个异常?
  6. P4781 【模板】拉格朗日插值
  7. MySQL 性能优化神器 Explain 使用分析
  8. ArcGIS 查看运行结果
  9. 为什么在使用超级终端配置交换机时显示乱码或无显示?
  10. Java Web项目源码整合开发大合集
  11. 动态链接库劫持--libc
  12. IIS站点出现503错误。
  13. Android实战——简单网络视频播放器
  14. 从软件外包到阿里技术专家再到CTO,他究竟是如何一路晋升?
  15. python3 常用模块_python3-常用模块之re
  16. NAS论文笔记:代理模型篇:NSGA Net V2: Evolutionary Multi-Objective Surrogate-Assisted Neural Architecture Sear
  17. mysql jdbc execute_MySQL JDBC Statement.executeBatch实践问题
  18. 制作大白菜装系统U盘以及重装系统
  19. 人到中年,到底应该坚持打工还是去创业?
  20. R3300L运行CoreELEC, EmuELEC和Armbian

热门文章

  1. css td 强制换行,CSS控制Table单元格强制换行与强制不换行
  2. iFunk,定义新基准
  3. vue手把手带你创建聊天室(vue-native-websocket)
  4. 开源GIS之WFS一:WFS介绍
  5. 手机平板电脑好用的九部形首末部件输入法
  6. 养老变身“坑老”? 找家靠谱养老机构为何这么难
  7. 第十二家面试(上海久科信息技术有限公司 )
  8. 白盒扫描自动化脚本关键语句
  9. 格密码学习笔记(六):格中模运算
  10. 服务器部署rz/sz