Linux静态链接库使用类模板的快速排序算法

快速排序的本质是从数组中选一个参考值ref,比该参考值的大的,将其放在ref的右边,比ref小的放在左边,然后不断的对两边重复执行该动作

我们先列出来快速排序的步骤:

1.从数组中选一个参考值ref,比该参考值的大的,将其放在ref的右边,

上面的动作将数组划分为两部分:

A ref B

A是比ref小的数组元素集合,它仍然是数组,B是比ref大的元素集合,它也仍然是数组

2.在对ref左右两边的元素重复上述动作,直到A和B都只剩下一个元素,那么排序就算完成了。

重点是如何分别选出来两个集合A和B。算法导论里面把这个步骤叫做partition动作。

先把算法导论里面的伪代码贴出来,大家先看一下:

先看第一种ref的选择方法,即ref = a[r]

partition(a[], p, r)
{
i = p
j = p-1
ref = a[r]
for(; i<r; i++)
{  
if(a[i]<ref)
{
j++
exchange(a[i], a[j])
}
}
exchange(a[r], a[j+1])
return j+1;
}

首先找一个参考值,ref = a[r],为了简单起见,这里取最后一个作为参考值,实际上可以去任意一个值作为参考值。这个我们一会再说。

然后找定义两个游标,分别是i 和j。i=p, j=p-1。为什么要这么定义呢?原因是我们既然选的是第一个,也就是a[p],同时表示是从数组的第一个元素开始遍历的。

选取j的目的是,我们要时刻知道当前最近一次比ref小的值的位置。由于我们选取的是a[r],作为参考值,且从第一个元素开始遍历,为了跟踪最近一次比ref小的数的游标,暂时j=p-1。大家可以仔细体会一下这个做的意义。

观察上述代码可以看到,j总是记录着最近一次比ref小的数的游标,因此最后return j+1,所有比ref小的数的游标均小于j+1,所有比ref大的数的游标均大于j+2。

总之我们执行partition的目的就是为了得到A,B,以及中间数的游标,这样我们就可以分别对A和B重复执行上述动作。

接下来我们考虑另外两种选取ref的方法。从上面选取最后一个值a[r],作为参考值,并且在最后,将a[r]和a[j+1]交换的动作可以知道,我们总是希望知道我们选取参考值在partition过程中的位置,以便我们可以在最后一步,将a[refId] 和 a[j+1]的值交换。这里的refId表示选取ref值在a[]中的游标。

如果我们选取ref为最后一个值,那么在所有的partition过程中,这个值的位置是固定的。但是,假如我们选取的ref的refId是p到r范围内的一个随机数呢?

显然,假如我们随机选取ref的值,那么在partition过程中,refId对于的ref就有可能和其他值交换。这时候我们就需要更新ref对应的游标。

这样一来,思路就很清晰了。

先给出partition的伪代码:

partition(a[], p, r){
refId = random(p,r)
i = p
j = p-1
for(; i<=r; i++)
{
if(a[i]<ref)
{
j++ if(j == refId)//此时j刚好等refId,并且要和a[i]交换,则更新refId { refId = i }
exchange(a[i], a[j])
}
}
exchange(a[j+1], a[refId])
return j+1
}

从三种选择ref的方法可以看到本质上都是一样的,都为用一个游标j记录最近一次遍历到的比ref小的数据的游标,然后将ref和a[j+1]交换,返回j+1。

下面给出C++的代码实现

#include <iostream>
#include <stack>
#include"stdlib.h"
#include <time.h>

using namespace std;
template<class T>
class SORT
{
public:
static void myQsort(T a[], int p, int r);
static void myQsortNoRecur(T a[], int p, int r);
private:
static int partition(T a[], int p, int r);
static void exchange(T a[], int i, int j);
};
template<class T>
void SORT<T>::exchange(T a[], int i, int j)
{
T temp = a[i];
a[i] = a[j];
a[j] = temp;
return;
}
template<class T>
int SORT<T>::partition(T a[],int p,int r)
{
int i = p;
int j = p-1;
T ref = a[p];
int refId = p;
srand((unsigned)time(NULL));
refId = (rand() % (r-p+1))+ p;
//cout<<refId<<endl;
ref = a[refId];
for(; i<=r; i++)
{
if(a[i] < ref)
{
j++;
exchange(a, i, j);
if(j == refId)
{
refId = i;
}
}
}
exchange(a, j+1, refId);
return j+1;
}
template<class T>
void SORT<T>::myQsort(T a[],int p,int r)
{
int q = 0;
if(p<r)
{
q = partition(a, p, r);
myQsort(a, p, q-1);
myQsort(a, p+1, r);
}
return;
}
template<class T>
void SORT<T>::myQsortNoRecur(T a[], int p, int r)
{
int start = p;
int end = r;
int mid = 0;
std::stack<int> sortStk;

sortStk.push(p);
sortStk.push(r);

while(!sortStk.empty())
{
end = sortStk.top();
sortStk.pop();
start = sortStk.top();
sortStk.pop();
if(start < end)
{
mid = partition(a, start, end);
sortStk.push(start);
sortStk.push(mid -1);
sortStk.push(mid + 1);
sortStk.push(end);
}
}
}
int main(int argc, char** argv)
{
int a[10];
int b[10];
srand((unsigned)time(NULL));
for(int i=0; i<9; i++)
{
a[i] = rand();
}

srand((unsigned)time(NULL));
for(int i=0; i<9; i++)
{
b[i] = rand();
}

SORT<int>::myQsort(a,0, 9);

SORT<int>::myQsortNoRecur(b,0, 9);

for(int i=0; i<10; i++)
{
cout<<a[i]<<" ";
}
cout<<endl;
for(int i=0; i<10; i++)
{
cout<<b[i]<<" ";
}
return 0;
}

上面的代码我直接给出了快速排序的递归和非递归实现。
递归的实现方式很好理解,但是加入别人正在面试你快速排序算法的时候,多半会让你用非递归的方式实现给他看。下面我们就讨论一下。

先观察一下递归算法的运行过程,即每次都去对一段更小的范围去调用partition函数。所以我们需要知道每一次调用partition函数的start和end游标,同时,每一次partition调用都会产生新的start和end游标。

template<class T>
void SORT<T>::myQsort(T a[],int p,int r)
{
int q = 0;
if(p<r)
{
q = partition(a, p, r);
myQsort(a, p, q-1);
myQsort(a, p+1, r);
}
return;

}

这样的话,我们就可以用一个通用容器去存放每次调用partition生成的start和end游标。知道虽有的合法的start和end游标都作为参数调用了partition函数。所谓合法的start和end就是start < end。。。。。

关于递归算法的非递归实现,第一个想到的数据结构肯定是栈。其实别的数据结构,例如队列,也是可以实现。这里给出基于stl内的stack的实现方法。代码如下

template<class T>
void SORT<T>::myQsortNoRecur(T a[], int p, int r)
{
int start = p;
int end = r;
int mid = 0;
std::stack<int> sortStk;

sortStk.push(p);
sortStk.push(r);

while(!sortStk.empty())
{
end = sortStk.top();
sortStk.pop();
start = sortStk.top();
sortStk.pop();
if(start < end)
{
mid = partition(a, start, end);
sortStk.push(start);
sortStk.push(mid -1);
sortStk.push(mid + 1);
sortStk.push(end);
}
}
}

上面代码的运行过程就是每次循环,从容器内拿一个start和end,如果是合法的,就依次将他们再次放入容易,知道这个容器为空未知。

给个运行实例吧,我在代码里面实现的是实现随机数排序,ref采用随机选取的方式。

(0)

相关推荐

  • Linux静态链接库与模板类的处理方式

    在阅读本文之前,小编先给大家介绍一篇相关文章:Linux静态链接库使用类模板的快速排序算法 大家首先看下以上的文章对理解下面的知识点会有很大的帮助. 当模板遇到静态链接库会发生什么呢. 我们先按照常规思路去考虑一个静态链接库的步骤: 1.将某些功能提取出来,放进一个cpp文件,并将接口或者对外导出的类放在头文件中 2.gcc -c编译该文件,生成.o 3.ar命令将.o文件打包成.a,即静态链接库 4.编译main函数,并将该静态链接库链接,生成可执行文件. OK,按照这个思路,我们将之前写的快

  • Linux下动态链接库加载路径及搜索路径问题

    引子 近日,服务器迁移后,偷懒未重新编译nginx的,直接./nginx启动,结果遇到如下问题: "error while loading shared libraries" 这是是因为需要的动态库不在动态链接器ld.so的搜索路径导致. ld.so 动态共享库搜索顺序 1.ELF可执行文件中动态段DT_RPATH指定:gcc加入链接参数"-Wl,-rpath"指定动态库搜索路径: 2.环境变量LD_LIBRARY_PATH指定路径: 3./etc/ld.so.ca

  • Python在Windows和在Linux下调用动态链接库的教程

    Linux系统下调用动态库(.so) 1.linuxany.c代码如下: #include "stdio.h" void display(char* msg){ printf("%s\n",msg); } int add(int a,int b){ return a+b; } 2.编译c代码,最后生成Python可执行的.so文件 (1)gcc -c linuxany.c,将生成一个linuxany.o文件 (2)gcc -shared linuxany.c -o

  • linux动态链接库使用方法分享

    1.前言 在实际开发过程中,各个模块之间会涉及到一些通用的功能,比如读写文件,查找.排序.为了减少代码的冗余,提高代码的质量,可以将这些通用的部分提取出来,做出公共的模块库.通过动态链接库可以实现多个模块之间共享公共的函数.之前看<程序员的自我修养>中讲到程序的链接和装入过程,这些玩意都是底层的,对于理解程序的编译过程有好处.http://www.ibm.com/developerworks/cn/linux/l-dynlink/博文介绍了程序的链接和装入过程.本文重点在于应用,如何编写和使用

  • Linux静态链接库使用类模板的快速排序算法

    快速排序的本质是从数组中选一个参考值ref,比该参考值的大的,将其放在ref的右边,比ref小的放在左边,然后不断的对两边重复执行该动作 我们先列出来快速排序的步骤: 1.从数组中选一个参考值ref,比该参考值的大的,将其放在ref的右边, 上面的动作将数组划分为两部分: A ref B A是比ref小的数组元素集合,它仍然是数组,B是比ref大的元素集合,它也仍然是数组 2.在对ref左右两边的元素重复上述动作,直到A和B都只剩下一个元素,那么排序就算完成了. 重点是如何分别选出来两个集合A和

  • GCC 编译使用动态链接库和静态链接库的方法

    1 库的分类 根据链接时期的不同,库又有静态库和动态库之分. 静态库是在链接阶段被链接的(好像是废话,但事实就是这样),所以生成的可执行文件就不受库的影响了,即使库被删除了,程序依然可以成功运行. 有别于静态库,动态库的链接是在程序执行的时候被链接的.所以,即使程序编译完,库仍须保留在系统上,以供程序运行时调用.(TODO:链接动态库时链接阶段到底做了什么) 2 静态库和动态库的比较 链接静态库其实从某种意义上来说也是一种粘贴复制,只不过它操作的对象是目标代码而不是源码而已.因为静态库被链接后库

  • 详解C++的JSON静态链接库JsonCpp的使用方法

    JsonCpp部署方法: 在http://sourceforge.net/projects/jsoncpp/中下载最新版本的jsoncpp库源码. 之后将jsoncpp-src-版本号-tar.gz解压出来,打开makefiles中的jsoncpp.sln进行编译,之后build文件夹下的vs71\debug\lib_json中会有一个.lib静态链接库. JsonCpp主要包含三种类型的class:Value Reader Writer. jsoncpp中所有对象.类名都在namespace

  • Go编译32位GNU静态链接库的方法

    Go链接库系统的难用可谓是人尽皆知,不同Go版本编译出来的不兼容,而且只支持GNU的,不能编译出Windows上的dll和lib. 本次有需求是将Go代码编译成32位GNU静态链接库. Go代码 编写代码如下: package main import "C" //export Add func Add(a, b int32) int32 { return a + b } func main() {} 注意我们必须把想要导出的函数显式使用//export Add注释标明,否则编译后不会产

  • C++封装静态链接库和使用的详细步骤

    目录 零碎记事 为什么要把程序封装成库 博主的环境 封装步骤 准备好待封装的程序 开始封装 配置项目 编译 找到编译好的静态库 打包 使用静态库使用步骤包含头文件 添加链接路径 源文件设置 项目设置 零碎记事 距离上次发博客已经有一年半了,转眼间我也是从做图像研究到了做游戏开发,说起来看看前面的博文,本来就有前兆的东西呢(笑)......因为主要还是在使用虚幻引擎,所以C++的东西会碰到多一些. 以后程序技术方面的文章就放博客,游戏设计相关的杂谈就放知乎那边吧,博主的知乎可以通过友链过去. B站

  • dev-c++创建lib(静态链接库)文件的实现步骤

    目录 第一步:制作静态链接库 第二步:链接静态链接库 方法一:使用项目 方法二:修改编译选项 第三步:使用库函数 方法一 方法二: 虽说dev-c++适合初学者,但是它的功能还是很强大的.那如何用它制作一个lib(静态链接库)呢? 第一步:制作静态链接库 1.打开dev-c++,选择“新建-项目”,如下图所示. 2.选择“Static Library”,并选择编程语言(c和c++无所谓)以及给项目设置名称. 3.选择你要保存的位置. 4.在新建的文件里添加函数,我这里添加了两个:一个叫hello

  • Linux静态库与动态库实例详解

    Linux静态库与动态库实例详解 1. Linux 下静态链接库编译与使用 首先编写如下代码: // main.c #include "test.h" int main(){ test(); return 0; } // test.h #include<iostream> using namespace std; void test(); // test.c #include "test.h" void test(){ cout<< &quo

  • 链接库动态链接库详细介绍

    windows中,链接库分为两种类型:静态链接库.lib和动态链接库.dll.其中动态链接库在被使用的时候,通常还提供一个.lib,称为引入库,它主要提供被Dll导出的函数和符号名称,使得链接的时候能够找到dll中对应的函数映射. 静态链接库和动态链接库的作用相似,都是提供给其他程序进行调用的资源.其中,动态链接库的调用方法分隐式调用(静态导入调用)和显示调用(动态导入调用). 编译环境: Microsoft Visual Stdio 2010 -------------------------

  • C++静态链接与动态链接详解

    目录 一.GCC工作流程 二.静态链接与动态链接 1.静态链接 2.动态链接 总结 一.GCC工作流程 预处理:把#头文件展开,进行宏替换,去掉注释(生成.i文件) 编译:把预处理后的文件生成汇编文件(.s文件),主要是检查语法.语义问题 汇编:把汇编文件生成目标文件(.o文件) 链接:将函数库中相应的代码组合到目标文件,生成可执行文件(默认a.out文件) o文件不会立即执行,因为可能出现:一个.cpp文件中的函数引用了另一个.cpp文件中定义的符号/调用了某个库文件中的函数.链接的目的就是将

随机推荐