首页 > 代码库 > 关于链表

关于链表

/*堆排序(大顶堆) 2011.9.14*/ 

 

#include <iostream>

#include<algorithm>

using namespace std;

 

void HeapAdjust(int *a,int i,int size)  //调整堆 

{

    int lchild=2*i;       //i的左孩子节点序号 

    int rchild=2*i+1;     //i的右孩子节点序号 

    int max=i;            //临时变量 

    if(i<=size/2)          //如果i是叶节点就不用进行调整 

    {

        if(lchild<=size&&a[lchild]>a[max])

        {

            max=lchild;

        }    

        if(rchild<=size&&a[rchild]>a[max])

        {

            max=rchild;

        }

        if(max!=i)

        {

            swap(a[i],a[max]);

            HeapAdjust(a,max,size);    //避免调整之后以max为父节点的子树不是堆 

        }

    }        

}

 

void BuildHeap(int *a,int size)    //建立堆 

{

    int i;

    for(i=size/2;i>=1;i--)    //非叶节点最大序号值为size/2 

    {

        HeapAdjust(a,i,size);    

    }    

 

void HeapSort(int *a,int size)    //堆排序 

{

    int i;

    BuildHeap(a,size);

    for(i=size;i>=1;i--)

    {

        //cout<<a[1]<<" ";

        swap(a[1],a[i]);           //交换堆顶和最后一个元素,即每次将剩余元素中的最大者放到最后面 

          //BuildHeap(a,i-1);        //将余下元素重新建立为大顶堆 

          HeapAdjust(a,1,i-1);      //重新调整堆顶节点成为大顶堆

    }

 

int main(int argc, char *argv[])

{

     //int a[]={0,16,20,3,11,17,8};

    int a[100];

    int size;

    while(scanf("%d",&size)==1&&size>0)

    {

        int i;

        for(i=1;i<=size;i++)

            cin>>a[i];

        HeapSort(a,size);

        for(i=1;i<=size;i++)

            cout<<a[i]<<"";

        cout<<endl;

    }

    return 0;

}

 

 

 

 

冒泡排序(Bubble Sort)是一种简单的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。

void bubbleSort(int *a,int len)

{

        int i,j,change;

 

        for(i=0;i<len;i++)

        {

               change=0;

               for(j=len-1;j>i;j--)

               {

                        if(a[j]<a[j-1])

                        {

                                change=1;

                                swap(&a[j],&a[j-1]);

                         }

               } 

               if(!change)

                        break;

        }

}

 

 

归并排序

void merge(int *a,int start,int mid,int end)

{

 

     if(start>mid || mid >end ) return;

 

     int i=start,j=mid+1,k=0;

     int *L=(int *)malloc((end-start+1)*sizeof(int));

 

     while(i<=mid && j<=end)

     {

          if(a[i]<a[j])

          {

              L[k++]=a[i++];

          }else          {

              L[k++]=a[j++];

          }

     }

       

     while(i<=mid)

          L[k++]=a[i++];

 

     while(j<=end)

          L[k++]=a[j++];

 

 

    for(i=start,j=0;i<=end;i++,j++)

    {

          a[i]=L[j];

    }

      

    free(L);

}

 

void mergeSort(int *a, int start,int end)

{

     if(start<end)

     {

          int mid=(start+end)/2;

          mergeSort(a,start,mid);

          mergeSort(a,mid+1,end);

          merge(a,start,mid,end);

     }

}

 

 

/*************************************************************************

  这是一个二叉查找树,实现了以下操作:插入结点、构造二叉树、删除结点、查找、

  查找最大值、查找最小值、查找指定结点的前驱和后继。上述所有操作时间复杂度

  均为o(h),其中h是树的高度

  注释很详细,具体内容就看代码吧

*************************************************************************/

 

#include<stdio.h>

#include<stdlib.h>

 

//二叉查找树结点描述

typedef int KeyType;

typedef struct Node

{

KeyType key;          //关键字

    struct Node * left;   //左孩子指针

struct Node * right;  //右孩子指针

struct Node * parent; //指向父节点指针

}Node,*PNode;

 

//往二叉查找树中插入结点

//插入的话,可能要改变根结点的地址,所以传的是二级指针

void inseart(PNode * root,KeyType key)

{

//初始化插入结点

PNode p=(PNode)malloc(sizeof(Node));

p->key=key;

p->left=p->right=p->parent=NULL;

//空树时,直接作为根结点

if((*root)==NULL){

*root=p;

return;

}

//插入到当前结点(*root)的左孩子

if((*root)->left == NULL && (*root)->key > key){

p->parent=(*root);

        (*root)->left=p;

return;

}

//插入到当前结点(*root)的右孩子

if((*root)->right == NULL && (*root)->key < key){

p->parent=(*root);

        (*root)->right=p;

return;

}

if((*root)->key > key)

inseart(&(*root)->left,key);

else if((*root)->key < key)

inseart(&(*root)->right,key);

else

return;

}

 

//查找元素,找到返回关键字的结点指针,没找到返回NULL

PNode search(PNode root,KeyType key)

{

if(root == NULL)

return NULL;

if(key > root->key) //查找右子树

return search(root->right,key);

else if(key < root->key) //查找左子树

return search(root->left,key);

else

return root;

}

 

//查找最小关键字,空树时返回NULL

PNode searchMin(PNode root)

{

if(root == NULL)

return NULL;

if(root->left == NULL)

return root;

else  //一直往左孩子找,直到没有左孩子的结点

    return searchMin(root->left);

}

 

//查找最大关键字,空树时返回NULL

PNode searchMax(PNode root)

{

if(root == NULL)

return NULL;

if(root->right == NULL)

return root;

else  //一直往右孩子找,直到没有右孩子的结点

    return searchMax(root->right);

}

 

//查找某个结点的前驱

PNode searchPredecessor(PNode p)

{

    //空树

if(p==NULL)

return p;

//有左子树、左子树中最大的那个

if(p->left)

     return searchMax(p->left);

//无左子树,查找某个结点的右子树遍历完了

else{

if(p->parent == NULL)

return NULL;

//向上寻找前驱

while(p){

     if(p->parent->right == p)

     break;

p=p->parent;

}

        return p->parent;

}

}

 

//查找某个结点的后继

PNode searchSuccessor(PNode p)

{

    //空树

if(p==NULL)

return p;

//有右子树、右子树中最小的那个

if(p->right)

     return searchMin(p->right);

//无右子树,查找某个结点的左子树遍历完了

else{

if(p->parent == NULL)

return NULL;

//向上寻找后继

while(p){

     if(p->parent->left == p)

     break;

p=p->parent;

}

        return p->parent;

}

}

 

//根据关键字删除某个结点,删除成功返回1,否则返回0

//如果把根结点删掉,那么要改变根结点的地址,所以传二级指针

int deleteNode(PNode* root,KeyType key)

{

PNode q;

//查找到要删除的结点

PNode p=search(*root,key);

KeyType temp;    //暂存后继结点的值

//没查到此关键字

if(!p)

return 0;

//1.被删结点是叶子结点,直接删除

if(p->left == NULL && p->right == NULL){

//只有一个元素,删完之后变成一颗空树

if(p->parent == NULL){

free(p);

(*root)=NULL;

}else{

//删除的结点是父节点的左孩子

     if(p->parent->left == p)

      p->parent->left=NULL;

     else  //删除的结点是父节点的右孩子

      p->parent->right=NULL;

free(p);

}

}

 

//2.被删结点只有左子树

else if(p->left && !(p->right)){

p->left->parent=p->parent;

//如果删除是父结点,要改变父节点指针

if(p->parent == NULL)

*root=p->left;

//删除的结点是父节点的左孩子

else if(p->parent->left == p)

         p->parent->left=p->left;

else //删除的结点是父节点的右孩子

p->parent->right=p->left;

free(p);

}

//3.被删结点只有右孩子

else if(p->right && !(p->left)){

p->right->parent=p->parent;

//如果删除是父结点,要改变父节点指针

if(p->parent == NULL)

*root=p->right;

        //删除的结点是父节点的左孩子

else if(p->parent->left == p)

         p->parent->left=p->right;

else //删除的结点是父节点的右孩子

p->parent->right=p->right;

free(p);

}

//4.被删除的结点既有左孩子,又有右孩子

//该结点的后继结点肯定无左子树(参考上面查找后继结点函数)

//删掉后继结点,后继结点的值代替该结点

    else{

//找到要删除结点的后继

q=searchSuccessor(p);

        temp=q->key;

//删除后继结点

        deleteNode(root,q->key);

p->key=temp;

}

return 1;

}

 

//创建一棵二叉查找树

void create(PNode* root,KeyType *keyArray,int length)

{

int i;

//逐个结点插入二叉树中

for(i=0;i<length;i++)

inseart(root,keyArray[i]);

}

 

int main(void)

{

int i;

    PNode root=NULL;

KeyType nodeArray[11]={15,6,18,3,7,17,20,2,4,13,9};

create(&root,nodeArray,11);

for(i=0;i<2;i++)

deleteNode(&root,nodeArray[i]);

printf("%d\n",searchPredecessor(root)->key);

printf("%d\n",searchSuccessor(root)->key);

printf("%d\n",searchMin(root)->key);

printf("%d\n",searchMax(root)->key);

printf("%d\n",search(root,13)->key);

return 0;

}

 

某本书上面说了,链表这个东西,实际用的并不多,但是可以提供很好的考察面试者编程技巧和思维能力的素材。这里总结一下,见过的面试题和对应的候选解法。

题一、 给定单链表,检测是否有环。

    使用两个指针p1,p2从链表头开始遍历,p1每次前进一步,p2每次前进两步。如果p2到达链表尾部,说明无环,否则p1p2必然会在某个时刻相遇(p1==p2),从而检测到链表中有环。

题二、 给定两个单链表(head1, head2),检测两个链表是否有交点,如果有返回第一个交点。

   如果head1==head2,那么显然相交,直接返回head1

   否则,分别从head1,head2开始遍历两个链表获得其长度len1len2。假设len1>=len2,那么指针p1head1开始向后 移动len1-len2步。指针p2=head2,下面p1p2每次向后前进一步并比较p1p2是否相等,如果相等即返回该结点,否则说明两个链表没有 交点。

题三、 给定单链表(head),如果有环的话请返回从头结点进入环的第一个节点。

   运用题一,我们可以检查链表中是否有环。

   如果有环,那么p1p2重合点p必然在环中。从p点断开环,方法为:p1=p, p2=p->next, p->next=NULL。此时,原单链表可以看作两条单链表,一条从head开始,另一条从p2开始,于是运用题二的方法,我们找到它们的第一个交点即为所求。

题四、只给定单链表中某个结点p(并非最后一个结点,即p->next!=NULL)指针,删除该结点。

   办法很简单,首先是放p中数据,然后将p->next的数据copyp中,接下来删除p->next即可。

题五、只给定单链表中某个结点p(非空结点),在p前面插入一个结点。

   办法与前者类似,首先分配一个结点q,将q插入在p后,接下来将p中的数据copyq中,然后再将要插入的数据记录在p中。

题六、给定单链表头结点,删除链表中倒数第k个结点。

   使用两个节点p1,p2p1初始化指向头结点,p2一直指向p1后第k个节点,两个结点平行向后移动直到p2到达链表尾部(NULL),然后根据p1删除对应结点。

题八、倒转单链表

给出非递归和递归解法:

#include <iostream>

 

using namespace std;

 

struct Node

{

    int data;

    Node* next;

}*head;

 

// 非递归写法

Node* InverseLinkedList(Node* head)

{

    if(head == NULL)

        return NULL;

    Node* newHead = NULL;

    while(head != NULL)

    {

        Node* nextNode = head->next;

        head->next = newHead;

        newHead = head;

        head = nextNode;

    }

    return newHead;

}

 

// 递归写法

Node* InverseLinkedListRecur(Node* head)

{

    if(head == NULL)

        return NULL;

    if(head->next == NULL)

        return head;

    Node* newHead = InverseLinkedListRecur(head->next);

    Node *tmp = newHead;

    while(tmp->next != NULL)

    {

        tmp = tmp->next;

    }

    tmp->next = head;

    head->next = NULL;

    return newHead;

}

 

int main()

{

    // 构建链表 9->8->7->6->5->4->3->2->1->0->NULL

    for(int i = 0; i < 10; i++)

    {

        Node* node = new Node();

        node->data = i;

        node->next = head;

        head = node;

    }

    head = InverseLinkedList(head);

    Node *tmp = head;

    while(head != NULL)

    {

        cout << head->data << " ";

        head = head->next;

    }

    cout << endl;

    head = InverseLinkedListRecur(tmp);

    tmp = head;

    while(head != NULL)

    {

        cout << head->data << " ";

        head = head->next;

    }

    cout << endl;

    while(tmp != NULL)

    {

        Node* next = tmp->next;

        delete tmp;

        tmp = next;

    }

}

题九、两个有序链表的合并

   有两个有序链表,各自内部是有序的,但是两个链表之间是无序的

typedef struct node{

    int data;

    struct node * next;

}* List;

 

List mergeSortedLinkList(List list1, List list2)

{

    List pList1,pList2,mergedList,pCurNode;

 

    if (list1 == NULL)

    {

        return list2;

    }

    if (list2 == NULL)

    {

        return list1;

    }

 

    pList1 = list1;

    pList2 = list2;

    mergedList = NULL;

    if (pList1==pList2)

          

        mergedList = pList1;

        pList1 = pList1->next;

        pList2 = pList2->next;

    }

    else 

    {

        if (pList1->data <= pList2->data)

        {

            mergedList = pList1;

            pList1 = pList1->next;

        }

        else

        {

            mergedList = pList2;

            pList2 = pList2->next;

        }

    }

    pCurNode = mergedList;

    while(pList1 && pList2)

    {

        if (pList1==pList2)

        {

            pCurNode->next = pList1;

            pCurNode = pList1;

            pList1 = pList1->next;

            pList2 = pList2->next;

        }

        else

        {

            if (pList1->data <= pList2->data)

            {

                pCurNode->next = pList1;

                pCurNode = pList1;

                pList1 = pList1->next;

            }

            else

            {

                pCurNode->next = pList2;

                pCurNode = pList2;

                pList2 = pList2->next;

            }

        }

    }

 

    pCurNode->next =pList1?pList1:pList2;

 

    return mergedList;

}

题十、找出链表的中间元素

单链表的一个比较大的特点用一句广告语来说就是“不走回头路”,不能实现随机存取(random access)。如果我们想要找一个数组a的中间元素,直接a[len/2]就可以了,但是链表不行,因为只有a[len/2 - 1] 知道a[len/2]在哪儿,其他人不知道。因此,如果按照数组的做法依样画葫芦,要找到链表的中点,我们需要做两步(1)知道链表有多长(2)从头结点开始顺序遍历到链表长度的一半的位置。这就需要1.5nn为链表的长度)的时间复杂度了。有没有更好的办法呢?有的。想法很简单:两个人赛跑,如果A的速度是B的两倍的话,当A到终点的时候,B应该刚到中点。这只需要遍历一遍链表就行了,还不用计算链表的长度。

 

 

面试的时候,书写程序要注意以下几点

1.确认了解题意,如果对题意了解不清,应该向面试人员问清楚
2.明确题意后,首先思考找到一个复杂度可以接受的正确算法,并表述出来,注意可以在草稿纸上写写划划,进行验证
3.观察复杂度能否再次降低
4.书写程序时,一定要认真,坚决防止出现逻辑错误,并根据程序具体分析可能的极端情况,处理好边界,并自己进行用例测试,以验证程序。

节点的定义如下:
typedef struct list {
int key;
struct list *next;
}list;

(1)已知链表的头结点head,写一个函数把这个链表逆序 ( Intel)

list * reverse(list * head){
list * h = head;
list * new_head = NULL,*temp;
if(h==NULL) return h;//如果是空链表
do{
   temp = h;
   h = h -> next;
   temp -> next = new_head;
   new_head = temp;
}while(h != NULL && h != head)//检测是循环链表
return new_head;
}

其实还要注意一点,链表内部是否包含小环。

(2)已知两个链表head1 head2 各自有序,请把它们合并成一个链表依然有序。(保留所有结点,即便大小

相同)

list * merge (list *list1_head, list *list2_head){

}

其实需要问一下,head1 head2是否都是从大到小,这点一定要明确,不能默认两个是相同的规格排序。

(3)已知两个链表head1 head2 各自有序,请把它们合并成一个链表依然有序,这次要求用递归方法进行。 (Autodesk)

list * merge (list *list1_head, list *list2_head){
   list * res;
   if(list1_head == NULL) return list2_head;
   if(list2_head == NULL) return list1_head; 
   if(list1_head->key > list2_head->key){
      res = list1_head;
      res->next = merge(list1_head->next,list2_head);

   }else{
      res = list2_head;
      res->next = merge(list1_head,list2_head->next);

   }
   return res;
}

 

(4)写一个程序,计算链表的长度

unsigned int list_len(list *head){
  unsigned int len = 0;
  list * h = head;
  if(h == NULL)return 0;
  d0{
      len++;
      h = h->next;
  }while(h != NULL && h != head)  
  return len;
}


如果是循环链表?

(5)有一个链表L,其每个节点有2个指针,一个指针next指向链表的下个节点,另一个random随机指向链表中的任一个节点,可能是自己或者为空,写一个程序,要求复制这个链表的结构并分析其复杂性。

这个题目的思路很巧妙。当然简单的方法,可以利用一个map或者hash,将链表的random指针的指向,保存起来。这样有O(n)存储空间的浪费,时间基本可以接近O(n).

实际上可以这样来做:我们将新的链表节点,插入到原来链表节点当中,并且修改原来的链表的random指针,使得该指针由我们现在的新节点所有。实际上形成下面这样一种结构,同时将原来orandom指针的值,复制给它后面的现在的@random指针,该结构如下:

o->@->o->@->o->@->NULL

现在可以利用@拥有的random指针方便的找到它真正的random指针。因为原来的@random指针指向orandom指针,只要把让@->random=@->random->next,random就是真正的那个指针了,然后我们再把@从这个链表中删除。

(6)给你一个单向链表的头指针,可能最后不是NULL终止,而是循环链表。题目问你怎么找出这个链表循环部分的第一个节点。

1。如果允许修改节点的数据结构的话,那么就在每个节点上设置一个标志位表示是否被访问过。这样遍历时遇到已访问节点即是循环的第一个节点。
2。如果不允许修改节点,那么就在外部用一个hashmap记录下所有的已访问节点。遍历时先查找这个hashmap,节点不存在则加入,已存在则该节点就是循环的第一个节点。


(7)(华为面试题)找出单向链表中中间结点
这个可以借鉴上面的方法,利用步长为1,步长为2指针,当步长为2的指针到达末尾时,指针1就刚好达到中间。


(8)题目:给定链表的头指针和一个结点指针,在O(1)时间删除该结点。链表结点的定义如下:
struct ListNode
{
      int        m_nKey;
      ListNode*  m_pNext;
};
函数的声明如下:
void DeleteNode(ListNode* pListHead, ListNode* pToBeDeleted);

实际上我们可以将该节点的下一个节点的值copy到这个节点,然后删除下一个节点。这样实际上就将一个不能处理的情况,通过一种技巧转化成了我们可以处理的正常情况。

但是还需要注意下面这个边界情况:如果要删除的是尾指针?这就意味着当前节点没有下一个节点。
通过这点我们也可以发现,如果认真考虑各个因素,思路清晰,还是很容易发现各种异常情况的,保证严谨的思考。

 

 

关于链表