数据结构与算法分析复习资料

本人自己总结的,水平有限,欢迎各位指点批评!

数据结构与算法分析复习资料

 

第一章.绪论

1.算法所关心的内容是什么?

时间复杂度和空间复杂度。Example:我们在一堆数据中查找一个我们所需要的数据,到底有多少种方法呢?

.将数据用数组(vector)存储起来,但排序时移动元素浪费时间;

‚.将数据用线性表list连接起来,但建表时相对麻烦,移动元素却十分方便;

ƒ.将数据建成查找二叉树(Binary-tree)

④.使用 ADT (抽象数据类型) map.

 

2.本课程的大体框架(Course Outline)

(1).C++ review

(2).Algorithmic asymptotic analysis

(3).Big-Oh, big-Theta, and big-Omega

(4).ADTs: Lists, stacks, and queues

(5).Sorting

    a.Insertion, mergesort, quicksort, heapsort, radix, etc.

    b.Lowest bound on sorting

(6).Search trees

    a.Binary search tree

    b.AVL tree

    c.B- tree

(7).Hashing

(8).Graphs

    a.Breadth-first search and Depth-first search

    b.Shortest  paths

    c.Minimal spanning trees

 

3.C++ review(指针和引用的相同点与不同点)

(1).从概念上讲。

      指针从本质上讲就是存放变量地址的一个变量,在逻辑上是独立的,它可以被改变,包括其所指向的地址的改变和其指向的地址中所存放的数据的改变。

      而引用是一个别名,它在逻辑上不是独立的,它的存在具有依附性,所以引用必须在一开始就被初始化,而且其引用的对象在其整个生命周期中是不能被改变的(自始至终只能依附于同一个变量)。

(2).★相同点:

●都是地址的概念;

指针指向一块内存,它的内容是所指内存的地址;而引用则是某块内存的别名。

    ★不同点:

●指针是一个实体,而引用仅是个别名;

●引用只能在定义时被初始化一次,之后不可变;指针可变;引用“从一而终”,指针可以“见异思迁”;

●引用没有const,指针有const,const的指针不可变;

●引用不能为空,指针可以为空;

●“sizeof 引用”得到的是所指向的变量(对象)的大小,而“sizeof 指针”得到的是指针本身的大小;

●指针和引用的自增(++)运算意义不一样.

(3).指针传递参数本质上是值传递的方式,它所传递的是一个地址值。值传递过程中,被调函数的形式参数作为被调函数的局部变量处理,即在栈中开辟了内存空间以存放由主调函数放进来的实参的值,从而成为了实参的一个副本。值传递的特点是被调函数对形式参数的任何操作都是作为局部变量进行,不会影响主调函数的实参变量的值。

(4)而在引用传递过程中,被调函数的形式参数虽然也作为局部变量在栈中开辟了内存空间,但是这时存放的是由主调函数放进来的实参变量的地址。被调函数对形参的任何操作都被处理成间接寻址,即通过栈中存放的地址访问主调函数中的实参变量。正因为如此,被调函数对形参做的任何操作都影响了主调函数中的实参变量。

 

第二章.程序性能

1、 给出一个程序段,会写出该程序段的时间复杂度;

 

第三章.线性表

1.抽象数据类型(ADT):是带有一组操作的一些对象的集合。例如包含像加(add),删除(remove),大小(size),以及包含(contains)这样一些操作。诸如表,集合,图以及与它们各自的操作一起形成的这些对象都可以看做是抽象数据类型。

 

2.对于vector,一般不要用erase函数,因为它会使删除点后后面的迭代器失效,因此使用了erase函数之后,“itr++”已经有问题了。但对删除点上和删除点前的迭代器却没有影响。因此应改为:“itr = qq.erase(itr);

 

3.确定一个链表只需要确定头结点(head)即可;

 

4.往List的前继插入

template<typename T>Node<T>* insertFront(Node<T> *&head, const T &x)

{     

head = new Node<T>(x,head);     

return head;

}

 

5.给定两个排序后的表L1L2,求两个表的交集IntersectionL1,L2

IntersectionL1,L2

{

int  i,j;

Result;

i = j = 0;

while (i < L1.size() && j < L2.size() )

{

if (L1[i] > L2[j])

j ++;

else if(L1[i] < L2[j])

i ++;

  else if (L1[i] == L2[j])

    {

Result.push_back(L1[i]);

i ++;

j ++;

}

}

return Result;

}

 

6.给定两个排序后的表L1L2,求两个表的交集MergeL1,L2

Merge(L1,L2)

{

int i,j;

i = j = 0;

Result;

while( i < L1.size() && j < L2.size() )

{

if( L1[i] < L2[j])

{

Result.push_back(L1[i]);

i ++;

}

else if(L1[i] > L2[j])

{

Result.push_back(L2[j]);

j++;

}

else

{

Result.push_back(L1[i]);

i++;

j++;

}

return Result;

}

(该例子应用于多项式的相加问题上)

 

7.顺序存储结构的特点

① 表中元素可以随机访问;

② 非表尾的插入和删除操作需要移动元素;

③ 表空间无法动态扩充,需预先按最大空间分配,存储空间利用率低;

④ 相邻元素的物理位置相邻

⑤ 无需为维护表中元素的逻辑关系增加额外的存储空间

1、 若用数组实现表,设表元素总数为n,在第k个元素后插入一个新元素,需要后移的元素数目为n-k。设在表的任何合法位置上插入元素的概率均等,则插入操作所需要的元素移动平均次数为n/2

2、 若用数组实现表,设表元素总数为n,删除第k个元素,需要前移的元素数目为n-k。设删除表的任何元素的概率均等,则删除操作所需要的元素移动平均次数为(n-1/2

8.链式存储结构的特点

① 表元素分散存放,物理位置不一定相邻;

② 每个结点包含指向下一结点的指针域;

③ 只能顺序搜索,不能随机访问;

④ 插入和删除操作只需要通过修改指针实现,效率较高

⑤ 不需为表预留空间,存储空间利用率高,表容量可动态扩充

⑥ 每个结点中的指针域需占用额外空间

3、 在带哨兵结点的单循环链表中,设置尾指针last指向链尾结点,每个结点中的后继指针域为next,则判断表空的表达式为last->next==last;在带哨兵结点的双循环链表中,设置指针header指向哨兵结点,每个结点中的后继指针域为next,前驱指针域为prior,则判断表空的表达式为header->next==headerheader->prior==header

4、 在带哨兵结点的单链表的插入和删除操作中,链指针的修改

5、 理解静态链表

① 静态链表中的指针域又称为游标或模拟指针,存放下一元素在数组中的下标

② 多条静态链表可共用一个数组空间

③ 在数组空间中,静态链表中的元素可以分散存放,即相邻元素的下标不一定相邻

④ 静态链表的扩充受到数组空间容量的限制

 

9.vectorlist的逻辑结构均是线性结构,存储结构分别为随机的和链式的。

这两种数据结构的特点:vector是一种随机存储结构,插入和删除平均需要移动一半的元素;list是链式存储结构,插入和删除只需要修改指针。或者说:vector适用于元素个数已知,需要随机存取,但是,插入和删除不多的场合。List适用于元素个数未知,随机存取不重要,但是插入和删除频繁的场合。

 

 

第三章.栈与队列

栈(stack):

1.栈又叫FILO表,操作原则为先进后出或后进先出,插入和删除操作均在栈顶方向上进行。

 

栈的应用:

1.对一些字符的倒序输出;

2.后缀表达式;

3.括号匹配问题(注意时刻检查栈是否为空);

4.中缀表达式转化成后缀表达式

      伪算法:例子:a+b*c+(d*e+f)*g

              A. 当遇到字母时,直接加入到输出串里面;

              B.当遇到的是'+'或'-'时,先看栈中的字符。若是‘*’或‘/'或'%',则先把栈顶元素出栈,再把后来的栈顶元素出栈,然后再把当前字符压栈;若是'+'或'-',则先把栈顶元素出栈再把当前符号压栈;

              C.当遇到的是’*' 或'/'或‘%'时,看栈顶元素。若栈顶元素是'+'或'-',则直接压栈;若是'*'或'/'或'%',则先将栈顶元素出栈,再把当前元素压栈。

              D.若遇到的是'(',则直接压栈(它的优先级最高);

              E.若遇到的是')',则弹出栈顶元素直到'('弹出;

      注意两点:a .记得适时检查栈是否为空;

                b .当栈顶元素还有元素时,记得把它们都读到输出字符串上。

 

队列(queue):

1.队列又叫FIFO表,只在队头有出队的操作,在队尾有入队的操作。

2.队列的实现通过循环数组来实现,也就是说在出队和入队的时候并不是真正的把元素删除。

 例如:

初始状态:

           

2

4

                                                   ↑        ↑

                                                 front    back 

1入队

1

         

2

4

   ↑                                                ↑        

back                                             front    

3入队

1

3

       

2

4

           ↑                                       ↑        

         back                                    front 

出队

1

3

       

2

4

           ↑                                                ↑        

         back                                             front 

出队

1

3

       

2

4

   ↑       ↑                                                        

  front   back                                             

 

3. 队列为空,则front==back;如果队列满了,(back+1)%N=front;数组中最后一个元素未利用,是为了区分空和满的情况  

 

 

第四章.树与查找

1.树的基本概念

  树:由N个结点和N-1条边的集合。有递归定义可以证明:每条边都将某个结点连接到它的父亲,而除去根结点外每一个结点都有一个父亲。

结点的度:分支的个数。

树的度:结点的度的最大值。

 

2.查找的三种方法:a. 顺序查找 ; b. 二分查找(先要有序) ; c. 哈希查找;

 

3.顺序查找的时间复杂度:O(n) ;二分查找的时间复杂度: O(logn) 。

 

4.二分查找的算法:

   int binSearch(const int *Array,int start,int end,int key)   

   {   

int left,right;

int mid;   

left=start;   

right=end;   

while (left<=right) { 

/注释中为递归算法,执行效率低,不推荐   

mid=(left+right)/2;   

/*if(key<Array[mid]) {   return(binSearch(Array,left,mid,key));     

else if(key>Array[mid]){  return (binSearch(Array,mid+1,right,key));}   

else   

return mid; */   

if (key<Array[mid]) 

  

right=mid-1;   

  

else if(key>Array[mid])

  

left=mid+1;   

  

else   

return mid;   

  

return -1;   

  

 

二叉查找树——Binary Search Trees (BST)

6.二叉查找树

大体上与二叉树相符,只是规定:(1若左子树不空,则左子树上所有结点的值均小于它的根结点的值; (2若右子树不空,则右子树上所有结点的值均大于它的根结点的值; (3)左、右子树也分别为二叉排序树; 

 

7.二叉查找树的特点:.查找终止于叶结点,总的比较次数包括查找成功的次数加查找失败的次数(两者相等,对半分);‚.所有的叶子必须在相邻的两层上;

 

8.如何计算总的比较次数和平均比较次数

 

 

 

 

 

 

 

 

 

 

 

 

总的比较次数(包括成功=失败的):

 (4*5)+(6*4)+(4*5)+(6*4) = 88(回归到叶,每个元素都是,即回归到正方形)

 

平均查找次数:

总的比较次数/结点数

 

完全二叉树(2-tree

9.完全二叉树(2-tree

也叫满二叉树,除最后一层无任何子节点外,每一层上的所有结点都有两个子结点(最后一层上的无子结点的结点为叶子结点)。也可以这样理解,除叶子结点外的所有结点均有两个子结点。节点数达到最大值。所有叶子结点必须在同一层上.

引理:满二叉树的每一层的结点数至多为上一层的两倍,因此t层高的树结点数至多为:2^t。

引理:如果满二叉树的第t层上有k个结点,因此:t>=lgk.

 

10.完全二叉树性质

具有n个结点的非空二叉树共有n-1个分支;

‚非空二叉树的第i层上至多有2i-1个结点(i1);

ƒ高度为h的非空二叉树至多有2h-1个结点

④高度为h的完全二叉树至多有2h-1个结点,至少有2h-1个结点

⑤若非空二叉树有n0个叶结点,n2个度为2的结点,则n0= n2+1

⑥具有n个结点的完全二叉树的高度为h=    +1

 

⑦具有n个结点的二叉树的最小高度为  +1,最大高度为n

⑧层数t= ,因为 t >= lg 2n  and t < lg 2n +1

⑨ The maximum number of key comparisons is t =, approximately lg n +1;

 

11.完全二叉树的观察及引理

Lemma1(引理). Let T be a 2-tree with k leaves. Then the height h of T satisfies h  dlg ke and the external path length E(T) satisfies E(T)>=k lg k. The minimum values for h and E(T) occur when all the leaves of T are on the same level or on two adjacent levels.

 

Lemma2. Suppose that an algorithm uses comparisons of keys to search for a target in a list. If there are k possible outcomes, then the algorithm must make at leastù comparisons  of keys in its worst case and at least lg k in its average case.

 

Lemma3. binary_search_1 is optimal(最佳的)in the class of all algorithms that search an ordered list by making comparisons of keys. In both the average and worst cases, binary_search_1 achieves the optimal bound.

 

12.二叉树基本概念:

 

*Path

*Length of a path

*Depth of a node

*The height of a tree = the depth of the deepest leaf

*Ancestor and descendant

 

13.树的的三种遍历方式

A.前序遍历:结点——左子树——右子树(对应前缀表达式)

B.中序遍历:左子树——结点——右子树(对应中缀表达式)

C.后序遍历:左子树——右子树——结点(对应后缀表达式)

 

14.对于BST几种算法的时间复杂度

Find(int Index) FindMin() FindMax() Insert()Delete()几个函数的时间复杂度均为

O(height of the tree)

 

15.满二叉树与完全二叉树的区别:满二叉树怎么要全满,但完全二叉树可以不全满(部分可放入下一层)。

16.性质 4 :具有 n 个结点的完全二叉树的深度为

17. 外部路径长度:根到叶子的距离之和。

   内部路径长度:根到内部结点(除了根和叶之外)的距离之和。

18.一个重要的证明题

对于含有n个内节点的二元树,证明E=2n+I。其中E,I分别为外部和内部路径长度。

证明:数学归纳法

①当n=1时,易知E=2,I=0,所以E=I+2n成立;

②假设n≤k(k>0)时,E=I+2n成立;

  则当n=k+1时,不妨假定找到某个内结点x为叶结点(根据二元扩展树的定义,一定存在这样的结点x,且设该结点的层数为h),将结点x及其左右子结点(外结点)从原树中摘除,生成新二元扩展树。此时新二元扩展树内部结点为k个,则满足E[k]=I[k]+2k,考察原树的外部路径长度为E[k+1]= E[k]-(h-1)+2h,内部路径长度为I[k+1]=I[k]+(h-1),所以E[k+1]= I[k]+2k+h+1= I[k+1]+2k+2= I[k+1]+2(k+1),综合①②知命题成立。

 

19.二叉树的算法:

BinaryNode *root;

void insert( const Comparable & x, BinaryNode * & t )//这里一定要用引用!因为在函数执行过程当中,root的值发生了改变。

{

if(t == NULL)

            t = new BinaryNode(x,NULL,NULL);

        else if(t->element > x)

            insert(x,t->left);

        else if(t->element < x)

            insert(x,t->right);

        else;

}

void remove(const Comparable & x, BinaryNode * & t)

{

if(t == NULL)

;

else if(x < t->element)

remove(x,t->left);

else if(x > t->element)

remove(x,t->right);

else if(x == t->element)

{

if(t->left == NULL && t->right==NULL)

{

BinaryNode* q = t;

t = NULL;

delete q;

}

else if(t ->left == NULL && t ->right != NULL)

{

BinaryNode* q = t;

t = t->right;

delete q;

}

else if(t ->right == NULL && t ->left != NULL)

{

BinaryNode* q = t;

t = t ->left;

delete q;

}

else if(t ->right != NULL && t ->left != NULL)

{

t ->element = findMin(t->right)->element;

remove(t->element,t->right);

}

}

}

    BinaryNode * findMin( BinaryNode *t ) const//注意这里不能用引用,因为不可以修改root的值因为它会返回一个值,不需要引用

    {

        while(t->left != NULL)

            t = t->left;

        return t;

}

BinaryNode * findMax( BinaryNode *t ) const

    {

        while(t->right != NULL)

            t = t->right;

        return t;

    }

    bool contains( const Comparable & x, BinaryNode *t ) const

    {

        while(t != NULL)

        {

            if(t -> element == x)

                return true;

            else if(t -> element > x)

                t = t -> left;

            else if(t -> element < x)

                t = t -> right;

        }

        return false;

    }

    void makeEmpty( BinaryNode * & t )

    {

        if( t != NULL )

        {

            makeEmpty( t->left );

            makeEmpty( t->right );

            delete t;

        }

        t = NULL;

    }

    void printTree( BinaryNode *t, ostream & out ) const

    {

        if( t != NULL )

        {

            printTree( t->left, out );

            out << t->element << endl;

            printTree( t->right, out );

        }

    }

    BinaryNode * clone( BinaryNode *t ) const

    {

        if( t == NULL )

            return NULL;

        else

            return new BinaryNode( t->element, clone( t->left ), clone( t->right ) );

}

int height(BinaryNode *t) const {

     if (!t)

         return -1;

       int l = height(t->left);

       int r = height(t->right);

       return (l>=r)? l+1: r+1;

    }

};                                 

20.//非递归方法中序遍历

void inorder3(BinaryNode *root){

stack<BST> qq;

while(root != NULL || !qq.empty())

{

while(root != NULL)

{

qq.push(root);

root = root -> left;

}

if(!qq.empty())

{

root = qq.top();

qq.pop();

cout << root->element << endl; //前序的这句话上插

root = root->right;

}

}

}

//前序:中序下面一句上插

//后序:中序的所有左和右一起颠倒

平衡查找二叉树(AVL-tree

1.任何结点的左子树的高度与右子树的高度的差的绝对值最多为1.

2.AVL查找等操作均为O(lgn).

3.具体的删除插入操作看第三次的作业。

4.记得删除内部结点的时候,用它的直接后继来取代。

 

B-树,B+树(多路查找树)

B+树:除了根节点外,所有结点必须保持半满条件;

      根节点要不就是一个叶子,要不就是就有至少两个的子结点;

      叶的深度也必须半满即:

      M:子结点的个数;  L:叶子的深度。

      B++树必须满足:M=L

      A  B+-tree  of  order M(L=M) has height

      操作具体看作业。

 

第五章.图

1.定义

  图G=(V,E)由顶点集V和边的集E组成。

  DAG:有向无环图。

2.图的出度与入度满足的关系:(其中n为顶点的个数,m为边的条数)

  (

3.图的几种表示方法:

  .邻接矩阵:空间需求:(适用于稠密图)

  ‚.邻接表:空间需求:(适用于稀疏图)

4.有向无环图:在图论中,如果一个有向图无法从某个顶点出发经过若干条边回到该点,则这个图是一个有向无环图(DAG图)。

5.强连通图:在有向图里面从每一个顶点到每个其他顶点都存在一条路径。

 

 

3.关于图的几种算法

拓扑排序

伪算法:首先,计算每个顶点的入度。然后,将所有入度为0的顶点放入一个初始为空的队列中。当队列不空时,删除一个顶点v,并将与v邻接的所有顶点的入度减1.只要顶点的入度将为0,就把该顶点放入队列中。

时间复杂度:O(m+n)(其中m为结点数,n为边数)

最短路径(无权)

BFS算法:

 

时间复杂度:邻接表:O(m+n)(其中m为结点数,n为边数)

            邻接矩阵:O(m^2)

最短路径(带权)

Dijkstra’s Algorithm

 

时间复杂度:不使用堆:O(V^2)(其中V为结点数,E为边数)

            使用堆:O(E*log(V))

怎么证明Dijkstra’s Algorithm的正确性呢?

证明:首先引进一个辅导向量D,它的每个分量D[i]表示当前所找到的从始点v到每个终点的最短路径长度。它的初态为:若从v有弧,则D[i]为弧上的权值,否则置D[i]。显然,长度为:

的路径就是从v出发的长度最短的一条最短路径。此路径为(v)。

所以假设S为已求得最短路径的终点的集合。则可证明:下一条最短路径(设其终点为x)或者为弧(v,x),或者是中间只经过S中的顶点而最后到达顶点x的路径。这可用反证法来证明:假设此路径上有一个顶点不在S中,则说明存在一条终点不在S而长度比此路径短的路径。但是,这是不可能的。因为我们是按路径长度递增的次序来产生各最短路径的,故长度比此路径短的所有路径均已产生,他们的终点必在S中,即假设不成立。命题得证。

 

最小生成树(Prims Algorithm

懂原理即可。关键是遍历每个点,每次只加一个点一条边。

Running time: O(|V|2+|E|) = O(|V|2 )

 

DFS算法

 

第六章.哈希表

1.解决冲突的方法有好几种:

.(使用链表)分离链接法:vector<list<int> >;

   在这里要分析:定义为散列表的装填因子,设为散列表中的元素个数与散列表大小的比值。则表的平均长度为:。在一次失败的查找中,考察次数为:;在一次成功的查找中,考察次数为:

 

‚.(不使用链表)探测散列表

替代位:

A.线性探测:  失败查找数:  成功次数:

B.平方探测:

C.双散列: ,

 

2.三种查找方法的特点分析:

A.二分查找  B.平衡二叉树  C.哈希表

适合动态查找(移动):BC

‚查找最大最小元素:AB

ƒ.查找性能不依赖于查找表的规模:C

④.不能输出元素的有序遍历:C

 

3.哈希表查找不成功的平均查找长度怎么计算?

解答:先建好表,然后可以算出每个位置不成功时的比较次数之和,再除以表空间个数!

 

例如:散列函数为hash(x)=x MOD 11,用线性探测,建立了哈希表之后,如何求查找不成功时的平均查找长度!?

 

地址:0 1 2 3 4 5 6 7 8 9 10

数据:33 1 13 12 34 38 27 22 - - -

成功次数:1 1 1 3 4 1 2 8

不成功次数:9 8 7 6 5 4 3 2 1 1 1

 

查找成功时的平均查找长度:ASL=(1+1+1+3+4+1+2+8)/8 =47/8

查找不成功时的平均查找长度:ASL=(9+8+7+6+5+4+3+2+1+1+1)/11

 

(注:求查找不成功时的平均查找长度,一般情况下分母为表长,但精确地讲是表长的有效位个数。

 

例如对于字符串来说,散列函数为hash(x)=x/2,x为字符的第一个字母在字母表的序号,表长即使为16,该分母也应取14,因为最大的hash(Z)=26/2=13,即只有0~13的14个有效位置有效。)

 

4.那么对于线性探测和平方探测,查找成功的次数和查找失败的次数又是如何计算?

  其实无论对于线性探测和平方探测,计算平均查找次数的原理是一样的:

  平均查找成功次数:

找准表上的每个元素,在哈希表上寻找定位,注意产生冲突时而要再定位时,只加1!不加上跳过的步数(因为那些点都不用探测),然后总的次数除以元素的总数即可;

  平均查找失败的次数:

对着哈希表的每个位置,开始探测,直到找到空格为止,从1加起,也是不是加上跳过的步数,然后总的次数除以表的长度即可。

 

 

第七章.排序

1.各种排序方法的时间复杂度及其时间性能:

 

.插入排序:时间复杂度:  (把某一元素先提出来,再通过移位,移位后在不提出来的元素重新插进去)

‚.归并排序:时间复杂度:(讲究递归分治)

 

 

 

ƒ.快速排序:时间复杂度:平均运行时间:,最坏情况:。有可能退化 

枢纽元(pivot),指针ij

 

 

④.堆排序: 最稳定的。

堆的概念:又叫完全二叉树,指除了最低一层不是满的,其他的都是满的。

步骤:a.先将序列按层次遍历建立二叉堆;(这里就满足几个关系:left child:2i;right child:2i+1,parent:floor(i/2)).

      b.再按需要进行调整成最大堆或最小堆;

      c.每次都在堆上删除根节点,再调整,同时把根节点信息存储起来,直到只剩下一个根节点为止,这样得到的序列便是有序的序列。

 

2.排序稳定性讨论

快速排序、堆排序不是稳定的排序算法,而插入排序、归并排序和基数排序是稳定的排序算法。

首先,排序算法的稳定性大家应该都知道,通俗地讲就是能保证排序前2个相等的数其在序列的前后位置顺序和排序后它们两个的前后位置顺序相同。在简单形式化一下,如果Ai = Aj, Ai原来在位置前,排序后Ai还是要在Aj位置前。

     其次,说一下稳定性的好处。排序算法如果是稳定的,那么从一个键上排序,然后再从另一个键上排序,第一个键排序的结果可以为第二个键排序所用。基数排序就是这样,先按低位排序,逐次按高位排序,低位相同的元素其顺序再高位也相同时是不会改变的。另外,如果排序算法稳定,对基于比较的排序算法而言,元素交换的次数可能会少一些(个人感觉,没有证实)

插入排序稳定性的证明(数学归纳法):比较是从有序序列的末尾开始,也就是想要插入的元素和已经有序的最大者开始比起,如果比它大则直接插入在其后面,否则一直往前找直到找到它该插入的位置。如果碰见一个和插入元素相等的,那么插入元素把想插入的元素放在相等元素的后面。所以,相等元素的前后顺序没有改变,从原无序序列出去的顺序就是排好序后的顺序,所以插入排序是稳定的。

归并排序:归并排序是把序列递归地分成短序列,递归出口是短序列只有1个元素(认为直接有序)或者2个序列(1次比较和交换),然后把各个有序的段序列合并成一个有序的长序列,不断合并直到原序列全部排好序。可以发现,在1个或2个元素时,1个元素不会交换,2个元素如果大小相等也没有人故意交换,这不会破坏稳定性。那么,在短的有序序列合并的过程中,稳定是是否受到破坏?没有,合并过程中我们可以保证如果两个当前元素相等时,我们把处在前面的序列的元素保存在结果序列的前面,这样就保证了稳定性。所以,归并排序也是稳定的排序算法。

快速排序:比如序列为 5 3 3 4 3 8 9 10 11, 现在中枢元素53(5个元素,下标从1开始计)交换就会把元素3的稳定性打乱,所以快速排序是一个不稳定的排序算法,不稳定发生在中枢元素和a[j]交换的时刻。

堆排序:比如:3 27 36 27
如果堆顶3先输出,则,第三层的27(最后一个27)跑到堆顶,然后堆稳定,继续输出堆顶,是刚才那个27,这样说明后面的27先于第二个位置的27输出,不稳定。

 

 

 

posted @ 2013-01-24 19:36  中大黑熊  阅读(1473)  评论(0编辑  收藏  举报