深入理解数据结构

转载出处:http://blog.csdn.net/andrexpert/article/details/77900395


Android                                          Java                                            数据结构
Android基础技术核心归纳(一)     Java基础技术核心归纳(一)     数据结构基础知识核心归纳(一) 
Android基础技术核心归纳(二)     Java基础技术核心归纳(二)     数据结构基础知识核心归纳(二)  
Android基础技术核心归纳(三)     Java基础技术核心归纳(三)    数据结构基础知识核心归纳(三)  
Android基础技术核心归纳(四)     

     

    不知不觉又是一年的9月,今天跟一个师弟聊天,谈到了他现在面试的一些情况,突然想起自己当年也是这么走过来的,顿时感慨良多。Android/Java经验汇总系列文章,是当初自己毕业时笔试、面试和项目开发中相关的总结,虽然不是很高深的东西,也没有归纳得很全面,但是对Android、算法、Java把握个大概还是没问题,今天特意将这些文章放出来,希望能够对看到这个系列文章的毕业生朋友一点帮助吧。当然,由于受当时知识面的限制,归纳得可能不是很准确,若有疑问就留言哈。


1.浅谈栈、队列、堆的区别?
1.堆:
堆是一种树状的数据结构。一般由程序员分配释放,存放由new创建的对象和数组(C中是由malloc分配和free释放),JVM不定时查看这个对象,如果没有引用指向这个对象就回收.

(1)优点:可动态分配内存大小,生成周期不必事先告诉编译器,Java垃圾回收自动回收数据;
(2)缺点:运行时需动态分配内存,因此,数据存储速度较慢

2.栈:是一种仅允许在一端进行插入和删除的线性表,即先进后出。由编译器自动分配释放,存放函数的参数值、局部变量的值等基本类型的变量和对象的引用。
(1)优点:存储速度比较块,仅次于寄存器且栈数据可以共享;
(2)缺点:存在栈中的数据大小与生存期必须是确定的(int y=4,y在栈中占4字节),缺乏灵活性
3.队列:是一种仅允许在尾端进行插入数据元素,首端进行删除数据元素的线性表,即先进先出FIFO。在队列中,数据元素可以任意增减,但数据元素的次序不会改变。每当有数据元素从队列中被取出,后面的数据元素一次向前移动一位。
参考:http://www.sxt.cn/u/1349/blog/2330
2.浅谈数组与链表的区别?
(1)从逻辑结构来看:数组必须实现定义固定的长度(元素个数),不能适应数据动态地增减的情况。当数据增加时,可能会造成溢出;当数据减少时,造成内存浪费,数组中插入、删除数据项时,需要移动其他数据项。链表动态地进行存储分配,可以使用数据动态地增减情况,可以节约内存,且可以方便地插入、删除数据数据项。
(2)从内存存储来看:(静态)数组从栈中分配空间(注:用new创建的在堆中分配),存储速度快但缺乏灵活性;链表从堆中分配空间,灵活性好但是申请管理比较麻烦;
(3)从访问方式来看:数组在内存中是连续存储的,因此可利用下标索引进行随机访问;链表是链式存储结构,在访问元素的时候只能通过线性的方法由前到后顺序访问,所以访问效率比数组要低。
3.单链表的创建、插入和删除操作?
1.单链表创建、插入、删除

(1)结点结构体


   
   
  1. /***struct Node *即存储的是类型数据地址
  2. 而后继结点即为struct Node类** */
  3. typedef struct
  4. {
  5. int data; //结点数据域 ,存放数据
  6. struct Node *next; //结点指针域,指针变量next存放struct Node *类型变量存储地址(指向结点)
  7. }Node,*LinkList; //Node表示结点,*LinkList 定义一个链表
(2)单链表读取操作
实现思想:首先,定义一个存储类型为结点地址的指针变量p,将第一个结点地址赋值给工作指针p。然后,初始化变量j并使其从1开始累加,当j<i时就遍历链表,工作指针p向后移动不断指向下一个结点,直到指向第i结点。若p指向null或者j大于要查询的位置时,查询失败;最后,取出第i个结点数据域中的数据,存放到指针变量e指向的存储空间。



   
   
  1. /*功能:从单链表中读取第i个元素,1<=i<=ListLenght(L)
  2. 存放到指针e指向的存储地址中*/
  3. #define UNSUCCESS -1
  4. #define SUCCESS 1
  5. typedef Status int;
  6. Status GetElem(LinkList L,int i,int *e)
  7. {
  8. int j= 1;
  9. LinkList p; //声明一个结点p
  10. p=L->next; //让结点p指向链表L的第一个结点
  11. while(p&&j<i) { //从链表第一个结点开始遍历,直到j=i-1,让p指向存储i-1位置所在的结点
  12. p=p->next; //让p指向下一个结点,
  13. ++j;
  14. }
  15. if(!p || j>i) //当p指向null或者j大于要查询的位置时,查询失败
  16. return UNSUCCESS ;
  17. *e=p->data; //查找成功,将链表中的第i个元素的数据存储到指针e指向的空间
  18. return SUCCESS;
  19. }
(3)单链表插入操作
实现思路:首先,对链表进行查询操作,找到第i个位置;其次,为新插入的结点s在内存中开辟一段存储空间并将插入的元素存储到新结点s的数据域中;最后,将p指向的结点位置赋值给新结点s的指针域,再将新结点s的存储地址赋值给工作指针。



   
   
  1. /*功能:将元素e插入到单链表的第i个位置*/
  2. #define UNSUCCESS -1
  3. #define SUCCESS 1
  4. typedef Status int;
  5. Status ListInsert(LinkList *L,int i,ElemType e)
  6. {
  7. int j= 1;
  8. LinkList p,s; //声明一个结点p
  9. p=*L; //让结点p指向链表L的第一个结点
  10. while(p&&j<i) { //从链表第一个结点开始遍历,直到j=i-1,让p指向存储i-1位置所在的结点
  11. p=p->next; //让p指向下一个结点
  12. ++j;
  13. }
  14. if(!p || j>i) //当p指向null或者j大于要查询的位置时,查询失败
  15. return UNSUCCESS ;
  16. s=(LinkList) malloc( sizeof(Node)); //生成新结点,即为新结点开辟一段内存
  17. s->data=e; //查找成功,将数据元素e赋值给s结点的数据域
  18. s->next =p->next; //将结点p的后继结点赋值给s的后继(p->next为指向结点p下一个存储地址所在的结点)
  19. p->next=s; //设置结点p的后继结点为结点s
  20. return SUCCESS ;
  21. }
(4)单链表删除操作
实现思想:首先,声明一个结点q并对链表进行查询操作,找到第i个位置;其次,将第i个结点赋值给q;第三,将q的后继(以前的p的后继)赋值为工作指针p的后继;最后,将第i个结点(q)数据域数据存储到指针变量e中,free(q)即释放结点q占用的内存空间。



   
   
  1. /***功能:删除单链表L中的第i个数据元素(结点)
  2. 并将该数据元素存储到指针变量e指向的存储空间中***/
  3. #define UNSUCCESS -1
  4. #define SUCCESS 1
  5. typedef Status int;
  6. Status ListInsert(LinkList *L,int i,ElemType *e)
  7. {
  8. int j= 1;
  9. LinkList p,q; //声明一个结点p
  10. p=*L;
  11. //从链表第一个结点开始遍历,直到j=i-1,让p指向存储i-1位置所在的结点
  12. while(p->next&&j<i)
  13. {
  14. p=p->next; //让p指向下一个结点
  15. j++;
  16. }
  17. if(!p || j>i)
  18. return UNSUCCESS;
  19. q=p->next; //设置q为p的后继结点,即将第i个结点的地址保存到q
  20. p->next=q->next; //将q的后继结点设置为p的后继结点
  21. *e=q->data; //将q结点中的数据给e
  22. free(q); //设置完成后,让系统回收此结点,释放内存
  23. return SUCCESS;
  24. }
注意:LinkList s为定义一个结点,同数组名一样,都可以直接用于表示地址。
2.创建一个单链表
实现思路:首先,定义一个头结点(*L),为其在内存中开辟一段空间并将指针域指针指向NULL;其次,生成一个新结点p,将要插入的数据元素存储到结点的数据域,并将其指针域指针指向头结点(*L)指向的结点(或NULL);最后,将新结点p插入到表头。


   
   
  1. /***
  2. 随机产生n个元素的值,建立带头结点的单链线性表L(头插法)***/
  3. //单链表节点的定义
  4. typedef struct{
  5. int data;
  6. struct Node* next;
  7. }Node;
  8. //由一个数组创建单链表
  9. Node* CreateList(int *a, int count)
  10. {
  11. if( NULL == a)
  12. return NULL;
  13. //头结点:开辟空间,赋值数据域、指针域
  14. Node* head = (Node*) malloc( sizeof(Node));
  15. head->data= a[ 0];
  16. head->next = NULL;
  17. //将链表的头结点赋值给指针变量p
  18. Node* p = head;
  19. for ( int i = 1; i < count; ++i)
  20. {
  21. p->next = (Node*) malloc( sizeof(Node));
  22. p->next->data= a[i];
  23. p->next->next = NULL;
  24. p = p->next;
  25. }
  26. return head; //返回链表
  27. }
    然后,在main函数中,实现如下代码:
    int a[]={5,9,3,1,2,7,8,6,4};
    Create_LinkList(a,sizeof(a)/sizeof(int));                 //传递结点的地址,结点数据,结点个数
3.线性表的链式存储性能分析
(1)查找:由于查找需通过指针从第一个结点开始查找,因此,最好情况时间复杂度为O(1),最坏情况事件复杂度为O(n)
(2)删除、插入:单链表的插入和删除主要由两部分组成:第一部分是遍历查询第i个元素;第二部分就是插入和删除元素
    ★从整个算法来说,我们很容易推导出:它们的时间复杂度都是O(n)。如果在我们不知道第i个元素的指针位置,单链表数据结构在插入和删除操作上,与线性表的顺序存储之间是没有太大的优势。但是如果我们希望从第i个位置,插入10个元素,对于顺序存储结构意味着,每一次插入都需要移动n-i个元素,每次都是O(n)。而对于单链表,我们只需要在第一次找到第i个位置的指针,此时为O(n),接下来只是简单地通过赋值移动指针而已,时间复杂度都是O(1)。
适用范围:对于插入或删除数据越频繁的操作,单链表的效率优势就越明显。

4.单链表结构与顺序存储结构的优缺点
◆存储分配方式
(1)顺序存储结构用一段连续的存储单元依次存储线性表的数据元素
(2)单链表采用链式存储结构,用一组任意的存储单元存放线性表的元素
◆时间性能
(1)查找:顺序存储结构O(1);单链表O(n)
(2)删除和插入:顺序存储结构需要平均移动表长一半的元素,时间为O(n);单链表在指出某位置的指针后,插入和删除时间仅为O(1)
◆空间性能
(1)顺序存储结构需要预分配存储空间,分大了,浪费,分小了易发生溢出;
(2)单链表不需要分配存储空间,只要有就可以分配,元素个数不受限制
5.头指针与头结点区别
(1)头指针:头指针是指向第一个结点存储位置的指针,具有标识作用(即常用头指针冠以链表的名字)。无论链表是否为空,头指针均不为空,是链表的必要元素。
(2)头结点:头结点放在第一个元素结点之前,方便在第一个元素结点前进行插入和删除操作。头结点的数据域可以不存储信息或存储线性表长度等信息,头结点不是链表的必要元素。


4.循环链表与双向链表?
 1.合并循环链表

    将单链表中终端结点的指针域由空指针改为指向头结点,就使整个单链表形成一个环,这种头尾相接的单链表称为单循环链表,简称循环链表。
(1)算法思想
    假设有A、B两个循环链表,且其尾指针分别为rearA、rearB。
a.使A循环链表的尾指针rearA指向(rearA->next)B循环链表的第一个结点(非头结点);
b.使B循环链表的尾指针rearB指向(rearB->next)A循环链表的头结点,即就链接成了一个新的循环链表。
(2)源码实现

   
   
  1. p=rearA->next; //声明一个结点p,用来保存A表的头结点
  2. rearA->next=rearB->next->next;
  3. //将本指向B表的第一个结点(不是头结点)赋值给rearA->next(原本指向A表的头结点)
  4. rearB->next=p; //将原A表的头结点赋值给rearB->next
  5. free(p); //释放p,因为p结点会占用内存资源

注:循环链表和单链表的主要差异就在于循环的判断条件上,即p->next是否为空。如果最后一个结点的指针域p->next为空则说明该链表为单链表;如果p->next等于头结点则说明该链表为循环链表,假如终端结点的指针域rear,那么rear->next指向头结点。   
2.双向链表
    双向链表是在单链表的每个结点中,再设置一个指向其前驱结点的指针域。与单链表的主要区别是,双链表中的结点都有两个指针域,一个指向直接后继,另一个指向直接前驱。用空间换来时间上的性能改进。
(1)插入一个元素
实现思路:
            a)创建新结点s,分别设置其直接后继、直接前驱
                        s->prior=p;         
                        s->next=p->next;
            b)将新结点s赋值给p->next结点的直接前驱指针(p->next->prior)
                        p->next->prior = s;
            c)将新结点s赋值p结点的直接后继指针(p->next)
                       p->next = s;(b.c不能互换,否则p->next->prior = p)
(2)删除一个元素
实现思路:

   
   
  1. p->prior->next=p->next; //把p->next赋值给p->prior的后继
  2. p->next->prior=p->prior; //把p->prior赋值给p->next的前驱
  3. free(p); //释放结点p占用的内存空间


5.单链表逆序(反转)与链表存在环路的判断(快慢指针)?
1.迭代循环算法–适合线性数据结构
(1)算法思想: 初始状态,prev是NULL,head指向当前的头节点A,next指向A节点的下一个节点B。首先从A节点开始逆序,将A节点的next指针指向prev,因为prev的当前值是NULL,所以A节点就从链表中脱离出来了,然后移动head和next指针,使它们分别指向B节点和B的下一个节点C(因为当前的next已经指向B节点了,因此修改A节点的next指针不会导致链表丢失),即完成结点A的逆向。然后,执行类型操作,分别逆向B、C、D结点,直到head指向Null位置。
部分伪代码如下:

   
   
  1. head->next = prev; //将A结点的next指向prev
  2. prev = head; //将指针prev指向A结点
  3. head = next; //将指针head指向B结点
  4. next = head->next; //将指针next(或head->next)指向C结点


(2)算法实现:

   
   
  1. 61 LINK_NODE *ReverseLink(LINK_NODE *head) //结点类型指针变量head指向单链表头结点
  2. 62 {
  3. 63 LINK_NODE *next;
  4. 64 LINK_NODE *prev = NULL; //初始条件
  5. 66 while(head != NULL) //终止条件
  6. 67 {
  7. 68 next = head->next; //指针next指向head->next结点
  8. 69 head->next = prev;
  9. 70 prev = head;
  10. 71 head = next;
  11. 72 }
  12. 74 return prev;
  13. 75 }
2..链表存在环路的判断
    单向链表中有环的话,如果我们对此链表进行遍历,则将无穷尽,因此有必要判断一个单向链表是否有环。双指针法
(1)算法思想:用两个指针开始都指向头节点,pA一次移动一个节点,pB一次向后移动两个节点,循环下去……每循环一次,如果pB==NULL,说明没有环(否则不会到达NULL),结束;如果pB==pA(转回来了),说明有环,结束。
(2)算法实现

   
   
  1. int RingJudge(LINK_NODE *head){
  2. LINK_NODE *p,*q;
  3. /* 链表的头指针为h */
  4. if(( NULL == head) || ( NULL == head->next)) /* 头指针为空或者链表中只有一个节点,则无环,退出 */
  5. {
  6. return 0;
  7. }
  8. p = q = head; /* 设p和 q 指针, 均指向头结点 */
  9. while( 1)
  10. {
  11. p = p->next;
  12. q = (q->next)->next;
  13. if(( NULL == p) || ( NULL == q))
  14. {
  15. printf(“No Ringn”); /* 链表中无环, 退出 */
  16. return 0;
  17. }
  18. if(p == q) /* 链表中有环 */
  19. {
  20. printf(“Ring occurred\n”);
  21. return 1;
  22. }
  23. }
  24. }
参考:http://blog.csdn.net/autumn20080101/article/details/7607148
6.未知长度单链表求倒数第K个元素?
1.算法思想

(1)指针a从链表头结点移动k个位置
(2)指针b从头结点开始移动,同时指针a从第k个位置开始移动,当a到达链表尾部时,指针b到达倒数第k个元素位置。   
2.算法代码实现
   设链表长度为n,a从k到尾部移动了n-k个元素,所以b也移动了n-k个元素,即b的位置为=n-(n-k)=k

   
   
  1. LINK_NODE *GetKElem(LINK_NODE *head,int k,ElemType *e){
  2. if(head == null){ //空链表
  3. return null;
  4. }
  5. LINK_NODE *first,*second; //声明俩个个指针,初始同时指向头结点
  6. first = second = head;
  7. /*
  8. *(1)判断k是否出界,若没出界则将指针first移动k个位置
  9. */
  10. while(k != 0){
  11. if(first == null) //链表元素个数小于k个
  12. return null;
  13. first = first->next;
  14. k--; //指针first从头结点开始移动k个位置
  15. }
  16. /*
  17. *(2)判断first是否到底,若没指针second开始从头指针移动,指针first从第k位置移动
  18. * 当first->next直到指向Null时,循环结束,指针second指向单链表的第K位置
  19. */
  20. while(first != null){
  21. first = first->next;
  22. second = second->next;
  23. }
  24. if(second != null && second != head) //将单链表的第K个位置的元素数据保存到指针e指向的地址空间
  25. *e = second->data;
  26. return second;
  27. }
7.单链表排序?时间复杂度[O(nlogn)],空间复杂度为常量
    链表排序和数组排序的思路类似,只是链表操作起来比较麻烦,因为不能随机访问,所以只能借助于类似于前置或后置插入,添加等概念来完成。
1. 插入排序(以从小到大排序为例)
(1)算法思想:链表排序最容易想到的是插入排序,它的基本想法是从第一个节点开始,每次将一个节点放到结果链表,并保证每个节点加入到结果链表前后,结果链表都是有序的。每个节点在被链入结果链表时有三种情况:
a.该节点值比结果链表中的所有元素值都大,则将该节点追加到结果链表的最后;
b.该节点值比结果链表中的所有元素值都小,则将该节点插入到结果链表最前面;
c.该节点值在结果链表中处于中间位置,则将该节点插入到合适位置。
    下面是该算法思路的流程图:

(2)算法代码实现

   
   
  1. LinkNode* SortLinkListInsertion(LinkNode* head)
  2. {
  3. //链表空或链表只有一个节点,不必排序,直接返回原头结点
  4. if ( NULL == head || NULL == head->next){
  5. return head;
  6. }
  7. //我们从第二个节点进行处理,第一个节点作为结果链表的初始节点
  8. LinkNode* r = head->next;
  9. LinkNode* tmp;
  10. head->next = NULL; //将结果链表末尾标记为结束
  11. while( NULL != r) { //依次处理每一个节点
  12. if (r->data < head->data) {
  13. //将该节点插到结果链表最前,并修改链表头,同时注意辅助变量的使用
  14. tmp = r->next;
  15. r->next = head;
  16. head = r;
  17. r = tmp;
  18. }
  19. else{
  20. //否则从链表头部开始搜索,注意这里搜索结束的条件是当前被搜索节点不是最后一个节点
  21. LinkNode* p = head;
  22. while( NULL != p->next){
  23. //注意只有当节点严格小于被搜索节点的下一节点的值是,才将其插入到被搜索节点后
  24. //这样能保证排序是稳定的!
  25. if (r->data < p->next->data) {
  26. tmp = r->next;
  27. r->next = p->next;
  28. p->next = r;
  29. r = tmp;
  30. continue; //注意跳出,开始处理下一个节点
  31. }
  32. else {
  33. p = p->next;
  34. }
  35. }
  36. //此时,p是结果链表中的末尾节点,将被处理节点追加到其后
  37. if ( NULL == p->next){
  38. tmp = r->next;
  39. r->next = NULL;
  40. p->next = r;
  41. r = tmp;
  42. }
  43. } //end else
  44. } //end while(NULL != r)
  45. return head;
  46. }
2.选择排序(以从小到大排序为例)
(1)算法思想:选择排序的基本思想是每次从源链表中选取并移除一个最小的元素,追加到结果链表的尾部,直到源链表变空为止。因此本算法的关键点在于如何从源链表中选取并移除一个最小的元素。考虑到一般情况下,我们在移除链表中的某个元素时,需要知道它的前一个节点的指针(我知道你在想那个trick,但我想我们还是用正统的方法吧),于是我们可以按照最小元素的出现位置分为两种情况:最小元素是链表头部节点和最小元素不是链表头部节点。我们先找出链表中除头结点外的最小值节点,然后再和头节点的值比较,然后进行处理。寻找除头结点外的最小值节点的代码可以很简洁,这也是为什么要分成这两部分处理的原因。
 
(2)算法代码实现

   
   
  1. LinkNode* SortLinkListSelection2(LinkNode* head)
  2. {
  3. //我们这里即使不进行特殊情况处理,代码也能正常工作,可以代入检查
  4. //if (NULL == head || NULL == head->next)
  5. //{
  6. // return head;
  7. //}
  8. LinkNode* p = NULL; //遍历辅助变量
  9. LinkNode* pminpre = NULL; //指向源链表中除头结点外的最小值节点的前驱节点
  10. LinkNode L = { 0, NULL}; //我们这里用了一个哑节点,它能简化后面的代码
  11. LinkNode* Ltail = &L; //Ltail用于指向结果链表的最后一个节点
  12. while ( NULL != head && NULL != head->next) //循环处理源链表中节点个数不小于2个的情况
  13. {
  14. pminpre = head;
  15. p = head->next;
  16. while( NULL != p && NULL != p->next) //找出源链表中除头结点外的最小值节点的前驱节点
  17. {
  18. if (p->next->val < pminpre->next->val) //严格小于时才改变pminpre
  19. pminpre = p;
  20. p = p->next;
  21. }
  22. if (head->val <= pminpre->next->val) //和头结点值进行比较处理,值相等时,取头结点
  23. {
  24. Ltail = Ltail->next = head;
  25. head = head->next;
  26. }
  27. else
  28. {
  29. Ltail = Ltail->next = pminpre->next;
  30. pminpre->next = pminpre->next->next;
  31. }
  32. }
  33. Ltail = Ltail->next = head; //最后一个节点直接追加到结果链表的尾部
  34. Ltail->next = NULL; //设置结果链表的结束标记
  35. return L.next;
  36. }
注意上面if语句中的 < 和 <= 判断,他们能使得链表的选择排序是稳定的排序。
 
3. 冒泡排序(以从小到大排序为例)
(1)算法思想:链表的冒泡排序不太好想,主要是不太好控制比较边界。这里我们用一个标记指针end表示边界,每完成一趟冒泡后,end会被向前移一下,直到完成N-1趟冒泡。冒泡需要比较并交换相邻的节点,因此我们在实现中使用了pre,cur,n等指针分别表示当前处理节点的前驱,当前处理节点和下一节点。

    链表冒泡排序的实现如下:
 

   
   
  1. //asscending sort link list
  2. LinkNode* SortLinkListBubble(LinkNode* head)
  3. {
  4. if ( NULL == head)
  5. {
  6. return head;
  7. }
  8. //init end pointer
  9. LinkNode* end = NULL
  10. while( true)
  11. {
  12. LinkNode* n = head->next;
  13. if (n == end)
  14. break;
  15. //这里单独处理头结点和第二个节点的比较,这是没有哑头节点的链表的一个劣势
  16. if (n->val < head->val)
  17. {
  18. head->next = n->next;
  19. n->next = head;
  20. head = n;
  21. }
  22. LinkNode* pre = head;
  23. LinkNode* cur = head->next;
  24. LinkNode* tmp;
  25. n = cur->next;
  26. while(n != end)
  27. {
  28. if (n->val < cur->val)
  29. {
  30. tmp = n->next;
  31. cur->next = n->next;
  32. n->next = cur;
  33. pre->next = n;
  34. n = tmp;
  35. }
  36. else
  37. {
  38. n = n->next;
  39. }
  40. pre = pre->next;
  41. cur = cur->next;
  42. }
  43. end = cur;
  44. }
  45. return head;
  46. }
4.快速排序(从小到大排序)
    知道链表还可以快速排序是在笔试某公司时才发现的,当时只看到了个QsortList什么的,而且是个填空题,当时没有思路,也没有做出来,只记得那个QsortList带了3个参数,还返回了个链接点指针。回来后细想了一阵,发现原理和数组的快速排序是一样的,只是链表在处理指针时比较麻烦,而且要保证排序后链表还是有序地链接在一起,不能出现掉链等情况,有些绕人。
(1)算法思想:从链表中选取一个节点(一般简单地取头结点),将其值作为pivot,将链表中剩余的节点分成两个子链表,其中一个中的所有值都小于pivot,另一个中的所有值都不小于pivot,然后将这两条链表分别链接到pivot节点的两端。然后对于子链表分别进行递归该过程,直到子链表中只有一个节点为止。
(2)代码实现
    下面给出了链表快速排序的一种实现方法,该实现没有返回参数,链表头也是通过引用方式传入的。

   
   
  1. void QsortList(LinkNode*& head, LinkNode* end)
  2. {
  3. if(head == NULL || head == end)
  4. return;
  5. LinkNode* pivot = head;
  6. LinkNode* p = head->next;
  7. LinkNode* head1 = NULL, *tail1 = NULL;
  8. LinkNode* head2 = NULL, *tail2 = NULL;
  9. while(p != end)
  10. {
  11. if(p->val < pivot->val)
  12. {
  13. if(head1 == NULL)
  14. {
  15. head1 = tail1 = p;
  16. }
  17. else
  18. {
  19. tail1->next = p;
  20. tail1 = p;
  21. }
  22. }
  23. else
  24. {
  25. if(head2 == NULL)
  26. {
  27. head2 = tail2 = p;
  28. }
  29. else
  30. {
  31. tail2->next = p;
  32. tail2 = p;
  33. }
  34. }
  35. p = p->next;
  36. }
  37. if (tail1)
  38. tail1->next = pivot;
  39. if (head2)
  40. pivot->next = head2;
  41. else
  42. pivot->next = end;
  43. if (tail2)
  44. tail2->next = end;
  45. if (head1)
  46. head = head1;
  47. else
  48. head = pivot;
  49. QsortList(head, pivot); //这里是传入head, 而不能传入head1,因为head还可能被子调用修改
  50. //同样这里传入pivot->next而非head2,这样才能保证最后链表是有序链在一起的
  51. QsortList(pivot->next, end);
  52. }
调用QsortList时采用这样的方式QsortList(L, NULL);
数组的快速排序是不稳定的,原因是其实现时采用了交换的机制;而链表的快速排序则是稳定的,其原因是,被扫描的节点是有序地依次添加到子链表的末尾,保证了两个等值节点的相对位置不变。
参考:http://blog.sina.com.cn/s/blog_6fb300a30100ng0s.html
8.两个单链表是否相交?
    判断两个链表是否相交,目的在于若两个链表出现相交的情况,假如程序释放了链表La的所有结点,将会导致另外一个与之有相交结点的链表Lb的结点也释放了。假设链表不存在环的情况下,两个链表相交的实质是:
    a.一旦连个链表相交,两个链表中的结点一定有相同的(内存)地址;
    b.一旦两个链表相交,两个链表从相交结点开始到尾结点一定都是相同的结点。

2.借助是否存在环判断
(1)算法分析
    如果两个链表中存在相交节点,那么将第二个链表接到第一个链表的后面,然后从第二个链表的表头开始遍历,如果存在环,则遍历过程一定会回到链表二的表头节点。
    这样两个链表就合成了一个链表,判断原来的两个链表是否相交也就转变成了判断新的链表是否有环的问题了:即判断单链表是否有环?—快慢指针:设置两个指针fast和slow,初始值都指向头,slow每次前进一步,fast每次前进二步,如果链表存在环,则fast必定先进入环,而slow后进入环,两个指针必定相遇。若fast指针先行头到尾部为Null,则为无环链表,两个链表不相交。
(2)性能分析

3.哈希解法
(1)算法思想
    既然连个链表一旦相交,相交节点一定有相同的内存地址,而不同的节点内存地址一定是不同的,那么不妨利用内存地址建立哈希表,如此通过判断两个链表中是否存在内存地址相同的节点判断两个链表是否相交。具体做法是:遍历第一个链表,并利用地址建立哈希表,遍历第二个链表,看看地址哈希值是否和第一个表中的节点地址值有相同即可判断两个链表是否相交。
(2)性能分析
  时间复杂度O(length1 + length2)
  空间复杂度O(length1)
注意:时间复杂度是线性的,相比直接法减少了时间复杂度,并且可以顺便找到第一个相交节点,但是却增加了O(length1)的空间复杂度,这显然不能令人满意。
4.直接法
    直接判断第一个链表的每个结点是否在第二个链表(遍历整个链表),时间复杂度为O(len1*len2),耗时非常大,不推荐使用。
参考:http://www.cnblogs.com/BeyondAnyTime/archive/2012/07/06/2580026.html
            http://blog.csdn.net/jiqiren007/article/details/6572685

    题目1:阅读下列函数说明和C代码,将应填进(n)处的字句写在答题纸的对应栏内。
【说明】设有一个带表头结点的双向循环链表L,每个结点有4个数据成员:指向先驱结点的指针prior、指向后继结点的指针next、存放数据的成员data和访问频度freq。所有结点的freq初始时都为0.每当在链表上进行一次L.Locate(x)操纵时,令元素值x的结点的访问频度freq加1,并将该结点前移,链接到现它的访问频度相等的结点后面,使得链表中所有结点保持按访问频度递减的顺序排列,以使频繁访问的结点总是靠近表头。
【函数】

   
   
  1. void Locate(int &x)
  2. {
  3. < 结点类型说明 >
  4. //遍历链表,找到元素值为x的结点
  5. *p = first->next;
  6. while (p != first && 1 p->data!=x )
  7. p = p->next;
  8. if (p != first)
  9. {
  10. 2 p->freq++; //找到结点后,将结点的访问频率freq加1
  11. < 结点类型说明 >
  12. *current = p;
  13. current->prior->next = current->next;
  14. current->next->prior = current->prior;
  15. p = current->prior;
  16. //向前遍历链表,链接到现它的访问频度相等的结点后面
  17. while (p != first && 3 p->freq<=x)
  18. p = p->prior;
  19. current->next = 4 p->next;
  20. current->prior = p;
  21. p->next->prior = current;
  22. p->next = 5 current;
  23. }
  24. else
  25. printf(“Sorry. Not find!\n”); \*没找到*\
  26. }

猜你喜欢

转载自blog.csdn.net/qq_25680531/article/details/80980427