数据结构综述(严蔚敏版)

一、算法的基本概念

1、算法的基本特征:可行性,确定性,有穷性,输入、输出。

2、算法的基本要素:算法中对数据的运算和操作、算法的控制结构。

3、算法设计的基本方法:列举法、归纳法、递推、递归、减半递推技术、回溯法。

4、算法设计的要求:正确性、可读性、健壮性、效率与低存储量需求

二、算法的复杂度

1、算法的时间复杂度T(n):指执行算法所需要的计算工作量

时间复杂度是总运算次数表达式中受n的变化影响最大的那一项(不含系数)

算法的基本操作重复执行的次数是模块n的某一个函数f(n),因此,算法的时间复杂度记做:T(n)=O(f(n))

在计算时间复杂度的时候,先找出算法的基本操作,然后根据相应的各语句确定它的执行次数,再找出T(n)的同数量级

它的同数量级有以下:常数阶O(1), 对数阶O(log2n),  线性阶O(n),(效率最高)线性对数阶O(nlog2n), 平方阶O(n^2), 立方阶O(n^3),..., k次方阶O(n^k), 指数阶O(2^n)

找出后,f(n)=该数量级,若T(n)/f(n)求极限可得到一常数c,则时间复杂度T(n)=O(f(n))

访问数组中的元素是常数时间操作,或说O(1)操作。一个算法如果能在每个步骤去掉一半数据元素,如二分检索,通常它就取O(logn)时间。用strcmp比较两个具有n个字符的串需要O(n)时间。常规的矩阵乘算法是O(n^3),因为算出每个元素都需要将n对元素相乘并加到一起,所有元素的个数是n^2。

具体见 //blog.csdn.net/firefly_2002/article/details/8008987

2、算法的空间复杂度S(n):执行这个算法所需要的内存空间

    S(n)=O(f(n)),其中,n为问题的规模,f(n)为语句关于n所占存储空间的函数

当一个算法的空间复杂度为一个常量,即不随被处理数据量n的大小而改变时,可表示为O(1);当一个算法的空间复杂度与以2为底的n的对数成正比时,可表示为0(10g2n);当一个算法的空间复杂度与n成线性比例关系时,可表示为0(n).若形参为数组,则只需要为它分配一个存储由实参传送来的一个地址指针的空间,即一个机器字长空间;若形参为引用方式,则也只需要为其分配存储一个地址的空间,用它来存储对应实参变量的地址,以便由系统自动引用实参变量。

三、数据结构的定义

1、数据的逻辑结构:集合、线性结构、树形结构、图形结构

2、数据的存储结构:顺序、链接、索引等

四、线性结构和非线性结构

    一般将数据结构分为两大类型:线性结构和非线性结构。

    线性结构:非空数据结构满足:有且只有一个根结点;每个结点最多有一个前件,最多只有一个后件。常见的线性结构:线性表、栈、队列

非线性结构:如果一个数据结构不是线性结构,称之为非线性结构。

五、线性表

线性表是n 个元素构成的有限序列(A1,A2,A3……)。表中的每一个数据元素,除了第一个以外,有且只有一个前件。除了最后一个以外有且只有一个后件。即线性表是一个空表,或可以表示为(a1,a2,……an), 其中ai(I=1,2,……n)是属于数据对象的元素,通常也称其为线性表中的一个结点。

1、线性表的顺序存储结构

    线性表的顺序表指的是用一组地址连续的存储单元依次存储线性表的数据元素。线性表的顺序存储结构具备如下两个基本特征:

(1)线性表中的所有元素所占的存储空间是连续的;

(2)线性表中各数据元素在存储空间中是按逻辑顺序依次存放的。

假设线性表的每个元素需占用K个存储单元,并以所占的第一个单元的存储地址作为数据元素的存储位置。则线性表中第i+1个数据元素的存储位置LOC(ai+1)和第i个数据元素的存储位置LOC(ai)之间满足下列关系:

LOC(ai+1)=LOC(ai)+K

LOC(ai)=LOC(a1)+(i-1)*K    

其中,LOC(a1)是线性表的第一个数据元素a1的存储位置,通常称做线性表的起始位置或基地址。

顺序表的插入运算

线性表的插入运算是指在表的第I个位置上,插入一个新结点x,使长度为n的线性表(a1,a2 …ai…an)变成长度为n+1的线性表(a1,a2…x,ai…an).该算法的时间主要花费在循环的结点后移语句上,执行次数是n-I+1。

当I=n+1,最好情况,时间复杂度o(1) 当I=1, 最坏情况,时间复杂度o(n)算法的平均时间复杂度为o(n)

顺序表的删除运算

线性表的删除运算是指在表的第I个位置上,删除一个新结点x,使长度为n的线性表(a1,a2 …ai…an)变成长度为n-1的线性表(a1,a2…ai-1,ai+1…an).

当I=n,时间复杂度o(1),当I=1,时间复杂度o(n) ,平均时间复杂度为o(n)

2、线性表的链式存储

一组任意的存储单元存储线性表的数据元素,因此,为了表示每个数据元素ai与其直接后继数据元素ai+1之间的逻辑关系,对数据元素ai来说,除了存储其本身的信息之外,还需存储一个指示其直接后继的信息(即直接后继的存储位置)。这两部分信息组成数据元素ai的存储映象,成为结点。它包括两个域:其中存储数据元素信息的域称为数据域,存储直接后继存储位置的域称为指针域。指针域中存储的信息称做指针或链。N个结点链结成一个链表,即为线性表(a1,a2,……,an)的链式存储结构。又由于此链表的每个结点中只包含一个指针域,故又称线性链表或单链表。

Struct node{

int data;

Node *next;

}*link;

链表的插入

  
 

链表的删除

  
 

六、栈

    1、栈是只能在表的一端进行插入和删除运算的线性表,通常称插入、删除这一端为栈顶(TOP),另一端为栈底(BOTTOM)。假设栈S=(a1,a2,a3,……an),则a1 称为栈底元素,an称为栈顶元素。栈中元素按a1,a2,a3……an的次序进栈,退栈的第一个元素应该是栈顶元素。即后进先出。



 

2、运算

入栈:在栈顶位置插入一个新元素。首先将栈顶指针进一(TOP+1),然后将新元素插入到栈顶指针指向的位置。



 

退栈:指取出栈顶元素并赋给一个指定的变量。首先将栈顶元素赋给一个指定的变量,然后将栈顶指针退一(TOP-1)



 

七、队列

队列是只允许在一端删除,在另一端插入的顺序表,允许删除的一端叫做队头,允许插入的一端叫做队尾。队列的修改是先进先出。往队尾插入一个元素成为入队运算。从对头删除一个元素称为退队运算。







 

八、循环队列

    就是将队列存储空间的最后一个位置绕到第一个位置,形成逻辑上的环状空间。在循环队列中,用队尾指针rear指向队列中的队尾元素,用排头指针front指向排头元素的前一个位置,因此,从排头指针front指向的后一个位置直到队尾指针rear指向的位置之间所有的元素均为队列中的元素。

在实际使用循环队列时,为了能区分队满还是队列空,通常需要增加一个标志S:

队列空,则S=0,rear=front=m    队列满,则S=1,rear=front=m

循环队列主要有两种基本运算:入队运算和退队运算

入队运算

指在循环队列的队尾加入一个新元素,首先rear=rear+1,当rear=m+1时,置rear=1,然后将新元素插入到队尾指针指向的位置。当S=1,rear=front,说明队列已满,不能进行入队运算,称为“上溢”。

退队运算

指在循环队列的排头位置退出一个元素并赋给指定的变量。首先front=front+1,并当front=m+1时,置front=1,然后将排头指针指向的元素赋给指定的变量。当循环队列为空S=0,不能进行退队运算,这种情况成为“下溢”。

九、树

1.每个结点只有一个前件,称为父结点,没有前件的结点只有一个,称为树的根结点。

2.每一个结点可以有多个后件结点,为该结点的子结点。没有后件的结点称为叶子结点

3.一个结点所拥有的后件个数称为树的结点度

4.树的最大层次称为树的深度。

十、二叉树

1、二叉树存储结构

   顺序存储



 

   链式存储


 

2、二叉树的基本性质

①在二叉树的第I层上至多有2i-1个结点。

②深度为k的二叉树至多有2k-1个结点(k>=1)

③在任意一个二叉树中,度为0的结点总是比度为2的结点多一个;

④具有n 个结点的二叉树,其深度至少为[log2n]+1。

    一棵深度为k且有2k-1个结点的二叉树称为满二叉树。这种树的特点是每一层上的结点数都是最大结点数。

2、满二叉树与完全二叉树

满二叉树:除最后一层以外,每一层上的所有结点都有两个子结点。在满二叉树的第K层上有2K-1个结点,且深度为M的满二叉树右2M-1个结点

完全二叉树:除最后一层以外,每一层上的结点数均达到最大值;在最后一层上只缺少右边的若干结点。具有N个结点的完全二叉树的深度为[log2n]+1

完全二叉树总结点数为N,若N为奇数,则叶子结点数为(N+1)/2;若N为偶数,则叶子结点数为N/2

3、二叉树的遍历

   1.前序遍历DLR 首先访问根结点,然后遍历左子树,最后遍历右子树。

   2.中序遍历LDR 首先遍历左子树,然后根结点,最后右子树

   3.后序遍历LRD 首先遍历左子树,然后遍历右子树,最后访问根结点。

4、赫夫曼树

用n(n>0)个带权值的叶子来构造二叉树,限定二叉树中除了这n个叶子外只能出现度为2的结点。那么符合这样条件的二叉树往往可构造出许多颗,其中带权路径长度最小的二叉树就称为哈夫曼树或最优二叉树



 

具体参见 //blog.csdn.net/shuangde800/article/details/7341289

十一、图(代码参见//blog.csdn.net/xiazdong/article/details/7354411)

1、概念

    图按照有无方向分为无向图和有向图。无向图由顶点和边构成,有向图由顶点和弧构成。弧有弧头和弧尾之分。

    图按照边或弧的多少分为稀疏图和稠密图。如果任意两个顶点之间都存在边叫完全图,有向的叫有向完全图。若无重复的边或顶点到自身的边则叫简单图。

    图中顶点之间有邻接点、依附的概念。无向图顶点的边数叫做度,有向图顶点分为入度和出度。

    图上的边或弧带权则称为网。

图中顶点间存在路径,两顶点存在路径则说明是连通的,如果路径最终回到起始点则称为环当中不重复的叫简单路径。若任意两顶点都是连通的,则图就是连通图,有向则称为环,当中不重复叫简单路径。若任意两顶点都是连通的,则图就是连通图,有向则称强连通图。图中有子图,若子图极大连通则就是连通分量,有向的则称为强连通分量。

无向图中的极大连通子图称为连通分量。注意连通分量的概念,它强调:

(1)要是子图;

(2)子图要是连通的;

(3)连通子图含有极大顶点数;

(4)具有极大顶点树的连通子图包含依附于这些顶点的所有边。

无向图中连通且n个顶点n-1条边叫生成树。有向图中一顶点的入度为0其余顶点的入度为1的叫有向树。一个有向图由若干棵有向树构成生成森林。

2、存储结构

    邻接矩阵

图的邻接矩阵存储方式是用两个数组来表示图。一个一维数组存储图中顶点信息,一个二维数组(邻接矩阵)存储图中的边或弧的信息。







 

 邻接表

    (1)图中顶点用一个一维数组存储,当然,顶点也可以用单链表来存储,不过,数组可以较容易的读取顶点的信息,更加方便。

(2)图中每个顶点vi的所有邻接点构成一个线性表,由于邻接点的个数不定,所以,用单链表存储,无向图称为顶点vi的边表,有向图则称为顶点vi作为弧尾的出边表。


 

顶点表的各个结点由data和firstedge两个域表示,data是数据域,存储顶点的信息,firstedge是指针域,指向边表的第一个结点,即此顶点的第一个邻接点。边表结点由adjvex和next两个域组成。adjvex是邻接点域,存储某顶点的邻接点在顶点表中的下标,next则存储指向边表中下一个结点的指针。




 

十字链表

十字链表,就是把邻接表和逆邻接表结合起来的。

重新定义顶点表结点结构,如下所示。其中firstin表示入边表头指针,指向该顶点的入边表中第一个结点,firstout表示出边表头指针,指向该顶点的出边表中的第一个结点。


 

重新定义边表结构,如下所示。其中,tailvex是指弧起点在顶点表的下标,headvex是指弧终点在顶点表的下标,headlink是指入边表指针域,指向终点相同的下一条边,taillink是指边表指针域,指向起点相同的下一条边。如果是网,还可以增加一个weight域来存储权值。




 

3、图的遍历

深度优先遍历

    深度优先遍历,也有称为深度优先搜索,简称DFS。其实,就像是一棵树的前序遍历。

    它从图中某个结点v出发,访问此顶点,然后从v的未被访问的邻接点出发深度优先遍历图,直至图中所有和v有路径相通的顶点都被访问到。若图中尚有顶点未被访问,则另选图中一个未曾被访问的顶点作起始点,重复上述过程,直至图中的所有顶点都被访问到为止。

广度优先遍历

    广度优先遍历,又称为广度优先搜索,简称BFS。图的广度优先遍历就类似于树的层序遍历了。

具体参见//m.blog.csdn.net/article/details?id=5996676

4、最小生成树

Prime

    假设G=(V,E)为一个带权图,其中V为带权图中结点的集合,E为带权图中边的权值集合。设置两个新的集合U和T,其中U用于存放带权图G的最小生成树的结点的集合,T用于存放带权图G的最小生成树的权值的集合。 

普里姆算法思想是:令集合U的初值为U={u0}(即假设构造最小生成树时从结点u0开始),集合T的初值为T={}。从所有结点u∈U和结点v∈V-U的带权边中选出具有最小权值的边(u,v),将结点v加入集合U中,将边(u,v) 加入集合T中。如此不断重复,当U=V时则最小生成树构造完毕。此时集合U中存放着最小生成树结点的集合,集合T中存放着最小生成树边的权值集合。


 

Kruskal

    克鲁斯卡尔算法是直接以边为目标去构建。因为权值是在边上,直接去找最小权值的边来构建生成树,只不过构建时要考虑是否会形成环路而已。此时我们用到了图的存储结构中的边集数组结构。

5、最短路径

Dijkstra(迪杰斯特拉)

    先把V分成两组:

    S:已求出最短路径的顶点的集合

    V-S=T:尚未确定最短路径的顶点集合   

    初使时令S={V0},T={其余顶点},T中顶点对应的距离值,若存在,为弧上的权值,若不存在,为Inf。

从T中选取一个其距离值为最小的顶点W(贪心体现在此处)加入S,对T中顶点的距离值进行修改:若加进W作中间顶点,从V0到Vi的距离值比不加W的路径要短,则修改此距离值。重复上述步骤,直到S中包含所有顶点,即S=V为止。

Floyd

从任意节点A到任意节点B的最短路径不外乎2种可能,1是直接从A到B,2是从A经过若干个节点到B,所以,我们假设dist(AB)为节点A到节点B的最短路径的距离,对于每一个节点K,我们检查dist(AK) + dist(KB) < dist(AB)是否成立,如果成立,证明从A到K再到B的路径比A直接到B的路径短,我们便设置 dist(AB) = dist(AK) +dist(KB),这样一来,当我们遍历完所有节点K,dist(AB)中记录的便是A到B的最短路径的距离。

十二、查找算法

1、顺序查找

   顺序比较,平均查找长度(n+1)/2,其中n为表长

2、折半查找

   待查表必须是有序的向量(在内存中连续存储)。首先和数组中点比较,如果等于则返回,如果小于中点则在左边区间查找,如果大于中点则在右边区间查找。平均查找长度lg(n+1)

3、分块查找

   抽取各块中的最大关键字及其起始位置构成一个索引表ID[l..b],即:ID[i](1≤i≤b)中存放第i块的最大关键字及该块在表R中的起始位置。由于表R是分块有序的,所以索引表是一个递增有序表。先用二分法查到元素可能所在的块起始位置,而后在块内进行顺序查找。平均查找长度在顺序查找和二分查找之间,并且当结点数为元素数量的平方根时,查找长度最小。

4、二叉树查找(B树)

   由如何改进二分查找的缺陷(插入和删除操作需要移动大量的数据)而得出的一种算法,用二叉排序树存储数据,由于二叉树的插入和删除操作的时间复杂度相对低,而且也支持二分查找,所以在动态数据查找方面优于二分查找。二叉树的特点是中序遍历可以得到递增的序列。很容易可以得出在二叉排序树上进行二分排序的递归代码。和二叉排序树的形态有关。在极端情况下,二叉树只有单一的左或右分支,则查找长度为(n+1)/2;如果是平衡二叉树,则查找长度为lgn(树的层次)。

   B树的插入

    首先执行查找算法,找出被插结点的父亲结点。

  判断被插结点是其父亲结点的左儿子还是右儿子。将被插结点作为叶子结点插入。

  若二叉树为空。则首先单独生成根结点。

  注意:新插入的结点总是叶子结点,所以算法复杂度是O(h)。

   B树的删除

  如果删除的结点没有孩子,则删除后算法结束;

  如果删除的结点只有一个孩子,则删除后该孩子取代被删除结点的位置;

如果删除的结点有两个孩子,则选择该结点的后继结点(该结点右孩子为根的树中的左子树中的值最小的点)作为新的根,同时在该后继结点开始,执行前两种删除算法,删除算法结束。

B+树一棵m阶的B+树满足下列条件:

(1)每个结点最多m个孩子。

(2)除根结点和叶子结点外,其它每个结点至少有ém/2ù个孩子。

(3)根结点至少有两个孩子。

(4)所有的叶子结点在同一层,且包含了所有关键字信息。

(5)有k个孩子的分支结点包含k个关键字。

5、哈希查找

   将元素的值和其位置直接对应,对应的方法就是散列函数(如直接定址,数字分析,平方取中,除余法等等);然而再好的散列函数也会引起冲突,则解决冲突的方法是如线性探测法(线性探测法的地址增量di= 1, 2, ... , m-1,其中,i为探测次数。该方法一次探测下一个地址,知道有空的地址后插入,若整个空间都找不到空余的地址,则产生溢出。),二次探测法(二次探测法的地址增量序列为di = 12,-12, 22, -22,… , q2, -q2 (q <= m/2)),链地址法(将所有具有相同哈希地址的而不同关键字的数据元素连接到同一个单链表中。如果选定的哈希表长度为m,则可将哈希表定义为一个有m个头指针组成的指针数组T[0..m-1],凡是哈希地址为i的数据元素,均以节点的形式插入到T[i]为头指针的单链表中。并且新的元素插入到链表的前端)等。

   装填因子= (哈希表中的记录数)/  (哈希表的长度)。装填因子是哈希表装满程度的标记因子。值越大,填入表中的数据元素越多,产生冲突的可能性越大。

十三、排序算法


 

1、直接插入算法

   将一个记录插入到已排序好的有序表中,从而得到一个新,记录数增1的有序表。即:先将序列的第1个记录看成是一个有序的子序列,然后从第2个记录逐个进行插入,直至整个序列有序为止。

   要点:设立哨兵,作为临时存储和判断数组边界之用。


 

2、希尔排序

   选择一个增量序列t1,t2,…,tk,其中ti>tj,tk=1;按增量序列个数k,对序列进行k趟排序;每趟排序,根据对应的增量ti,将待排序列分割成若干长度为m 的子序列,分别对各子表进行直接插入排序。仅增量因子为1时,整个序列作为一个表来处理,表长度即为整个序列的长度。

3、简单选择排序

   在要排序的一组数中,选出最小(或者最大)的一个数与第1个位置的数交换;然后在剩下的数当中再找最小(或者最大)的与第2个位置的数交换,依次类推,直到第n-1个元素(倒数第二个数)和第n个元素(最后一个数)比较为止。

4、冒泡排序

   在要排序的一组数中,对当前还未排好序的范围内的全部数,自上而下对相邻的两个数依次进行比较和调整,让较大的数往下沉,较小的往上冒。即:每当两相邻的数比较后发现它们的排序与排序要求相反时,就将它们互换。

5、快速排序

   1)选择一个基准元素,通常选择第一个元素或者最后一个元素,

   2)通过一趟排序讲待排序的记录分割成独立的两部分,其中一部分记录的元素值均比基准元素值小。另一部分记录的元素值比基准值大。

   3)此时基准元素在其排好序后的正确位置

   4)然后分别对这两部分记录用同样的方法继续进行排序,直到整个序列有序。

6、堆排序

   初始时把要排序的n个数的序列看作是一棵顺序存储的二叉树(一维数组存储二叉树),调整它们的存储序,使之成为一个堆,将堆顶元素输出,得到n个元素中最小(或最大)的元素,这时堆的根节点的数最小(或者最大)。然后对前面(n-1)个元素重新调整使之成为堆,输出堆顶元素,得到n个元素中次小(或次大)的元素。依此类推,直到只有两个节点的堆,并对它们作交换,最后得到有n个节点的有序序列。称这个过程为堆排序。

   实现堆排序需解决两个问题:

  (1) 如何将n 个待排序的数建成堆;

  (2)输出堆顶元素后,怎样调整剩余n-1个元素,使其成为一个新堆。

调整小顶堆的方法:

1)设有m 个元素的堆,输出堆顶元素后,剩下m-1个元素。将堆底元素送入堆顶((最后一个元素与堆顶进行交换),堆被破坏,其原因仅是根结点不满足堆的性质。

2)将根结点与左、右子树中较小元素的进行交换。

3)若与左子树交换:如果左子树堆被破坏,即左子树的根结点不满足堆的性质,则重复方法 (2).

4)若与右子树交换,如果右子树堆被破坏,即右子树的根结点不满足堆的性质。则重复方法 (2).

5)继续对不满足堆性质的子树进行上述交换操作,直到叶子结点,堆被建成。

建堆方法:对初始序列建堆的过程,就是一个反复进行筛选的过程。

1)n 个结点的完全二叉树,则最后一个结点是第个结点的子树。

2)筛选从第个结点为根的子树开始,该子树成为堆。

3)之后向前依次对各结点为根的子树进行筛选,使之成为堆,直到根结点。

7、归并排序

   归并(Merge)排序法是将两个(或两个以上)有序表合并成一个新的有序表,即把待排序序列分为若干个子序列,每个子序列是有序的。然后再把有序子序列合并为整体有序序列。

   设r[i…n]由两个有序子表r[i…m]和r[m+1…n]组成,两个子表长度分别为n-i+1、n-m。

    j=m+1;k=i;i=i;//置两个子表的起始下标及辅助数组的起始下标

    若i>m或j>n,则排序完毕//其中一个子表已合并完,比较选取结束

    //选取r[i]和r[j]较小的存入辅助数组rf

    如果r[i]

    否则,rf[k]=r[j];j++; k++; 转⑵

    //将尚未处理完的子表中元素存入rf

    如果i<=m,将r[i…m]存入rf[k…n]//前一子表非空

    如果j<=n,  将r[j…n] 存入rf[k…n] //后一子表非空

    合并结束。

8、基数排序

   基数排序(RadixSort)属于分配式排序,又称"桶子法"(Bucket Sort或BinSort),将要排序的元素分配到某些"桶"中,以达到排序的作用。

   基数排序的方式可以采用LSD(Leastsgnificant digital)或MSD(Most sgnificantdigital),LSD的排序方式由键值的最右边开始,而MSD则相反,由键值的最左边开始。LSD的基数排序适用于位数小的数列,如果位数多的话,使用MSD的效率会比较好,MSD的方式恰与LSD相反,是由高位数为基底开始进行分配,其他的演算方式则都相同。

具体参见//m.blog.csdn.net/article/details?id=7776068

#include
#include
#include
#include
using namespace std;

void swap(char *a,char *b)
{
    chartemp;
   temp=*a;
    *a=*b;
   *b=temp;
}
//交换
void insertsort(char *a,int n)
{
    inti,j,key;
   for(i=1;i
    {
      if(a[i]
       {
         key=a[i];
         for(j=i-1;a[j]>key&&j>=0;j--)
            a[j+1]=a[j];
         a[j+1]=key;
       }
    }
}
//插入排序

void bubblesort(char *a,int n)
{
    inti,j;
    chartemp;
   for(i=0;i
      for(j=0;j
            if(a[j]>a[j+1])
             {
               temp=a[j];
               a[j]=a[j+1];
               a[j+1]=temp;
             }
}
//冒泡排序

void selectsort(char *a,int n)
{
    inti,j,k;
    chartemp;
   for(i=0;i
    {
       k=i;
      for(j=i+1;j
         if(a[j]
       if(k!=i){temp=a[i];a[i]=a[k];a[k]=temp;}
    }
}
//简单选择排序

int partition(char *a,int low,int high)
{
    chartemp=a[low];
   while(low
    {
      while(low=temp) high--;
      swap(&a[low],&a[high]);
      while(low<=temp) low++;
      swap(&a[low],&a[high]);
    }
    returnlow;
}
void quicksort(char *a,int low,int high)
{
    if(low
    {
       intpivot=partition(a,low,high);
      quicksort(a,low,pivot-1);
      quicksort(a,pivot+1,high);
    }
}
//快速排序
 
void merge(char *r,int s,int m,int t)
{
    inti,j,k;
    chara[20],b[20];
   for(i=0;i<=m-s;i++) a[i]=r[s+i];
   for(j=0;j
   for(k=s,i=0,j=0;i<=m-s&&j
    {
      if(a[i]
       elser[k]=b[j++];     }    while(i<=m-s) r[k++]=a[i++];    while(j } void mergesort(char *r,int s,int t) {     intm=(s+t)/2;     if(s     {       mergesort(r,s,m);       mergesort(r,m+1,t);       merge(r,s,m,t);     } } //归并排序 void heapify(char *a,int i,int n) {     intl,r,largest;    l=2*i+1;    r=2*i+2;     if(la[i])largest=l;     elselargest=i;    if(ra[largest]) largest=r;    if(largest!=i){swap(&a[i],&a[largest]);heapify(a,largest,n);} } void buildheap(char *a,int n) {     int i;    for(i=n/2-1;i>=0;i--)       heapify(a,i,n); } void heapsort(char *a,int n) {     int i;    buildheap(a,n);    for(i=n-1;i>0;i--)        {          swap(&a[0],&a[i]);          heapify(a,0,i);        } } //堆排序 void main() {     chara[20];     int n;    while(cin>>a)     {       n=strlen(a);       //insertsort(a,n);       //bubblesort(a,n);       //selectsort(a,n);       quicksort(a,0,n-1);       //mergesort(a,0,n-1);       //heapsort(a,n);       cout<<a;       getchar();     } }