跳跳魔王的技术博客

科技改变生活


  • 首页

  • 归档

链表 | 第11课(合并两个有序链表)

发表于 2018-03-21

变形一个 SortedMerge() 函数,带有两个链表参数, 每个链表都是按照升序排列, 然后把这两个链表合并成一个升序的链表。 SortedMerge() 应该返回一个全新的链表。新链表应该把两个链表的结点拼接起来。

例如第一个链表是 5->10->15 第二个链表是 2->3->20, 然后 SortedMerge() 应该返回 2->3->5->10->15->20.

有很多情况需要处理: ‘a’ 或者 ‘b’ 可能为空, 处理 ‘a’ 或 ‘b’ 过程中可能首先跑完某个, 最后的问题是开始结果是空, 然后通过‘a’ 和 ‘b’ 把它建立起来。

方法 1 (使用仿制结点)

这里的策略是使用一个临时的仿制结点,作为结果链表的开始。Tail 指针总是表示结果链表的最后一个结点, 所以添加新结点很容易。
在结果链表是空的时候,仿制结点会给尾部指针一些指向作为初始化。仿制结点是有效的, 因为它只是临时的, 而且它在栈内分配。循环进行, 从 ‘a’ 或者 ‘b’ 移除一个结点, 然后添加到尾部。当完事后,结果就保存在 dummy.next.

/* C/C++ program to merge two sorted linked lists */
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>

/* Link list node */
struct Node
{
    int data;
    struct Node* next;
};

/* pull off the front node of the source and put it in dest */
void MoveNode(struct Node** destRef, struct Node** sourceRef);

/* Takes two lists sorted in increasing order, and splices
   their nodes together to make one big sorted list which
   is returned.  */
struct Node* SortedMerge(struct Node* a, struct Node* b)
{
    /* a dummy first node to hang the result on */
    struct Node dummy;

    /* tail points to the last result node  */
    struct Node* tail = &dummy;

    /* so tail->next is the place to add new nodes
      to the result. */
    dummy.next = NULL;
    while (1)
    {
        if (a == NULL)
        {
            /* if either list runs out, use the
               other list */
            tail->next = b;
            break;
        }
        else if (b == NULL)
        {
            tail->next = a;
            break;
        }
        if (a->data <= b->data)
            MoveNode(&(tail->next), &a);
        else
            MoveNode(&(tail->next), &b);

        tail = tail->next;
    }
    return(dummy.next);
}

/* UTILITY FUNCTIONS */
/* MoveNode() function takes the node from the front of the
   source, and move it to the front of the dest.
   It is an error to call this with the source list empty.

   Before calling MoveNode():
   source == {1, 2, 3}
   dest == {1, 2, 3}

   Affter calling MoveNode():
   source == {2, 3}
   dest == {1, 1, 2, 3} */
void MoveNode(struct Node** destRef, struct Node** sourceRef)
{
    /* the front source node  */
    struct Node* newNode = *sourceRef;
    assert(newNode != NULL);

    /* Advance the source pointer */
    *sourceRef = newNode->next;

    /* Link the old dest off the new node */
    newNode->next = *destRef;

    /* Move dest to point to the new node */
    *destRef = newNode;
}


/* Function to insert a node at the beginging of the
   linked list */
void push(struct Node** head_ref, int new_data)
{
    /* allocate node */
    struct Node* new_node =
        (struct Node*) malloc(sizeof(struct Node));

    /* put in the data  */
    new_node->data  = new_data;

    /* link the old list off the new node */
    new_node->next = (*head_ref);

    /* move the head to point to the new node */
    (*head_ref)    = new_node;
}

/* Function to print nodes in a given linked list */
void printList(struct Node *node)
{
    while (node!=NULL)
    {
        printf("%d ", node->data);
        node = node->next;
    }
}

/* Drier program to test above functions*/
int main()
{
    /* Start with the empty list */
    struct Node* res = NULL;
    struct Node* a = NULL;
    struct Node* b = NULL;

    /* Let us create two sorted linked lists to test
      the functions
       Created lists, a: 5->10->15,  b: 2->3->20 */
    push(&a, 15);
    push(&a, 10);
    push(&a, 5);

    push(&b, 20);
    push(&b, 3);
    push(&b, 2);

    /* Remove duplicates from linked list */
    res = SortedMerge(a, b);

    printf("Merged Linked List is: \n");
    printList(res);

    return 0;
}

#
输出 :

Merged Linked List is: 
2 3 5 10 15 20 

方法 2 (使用本地引用)

这个方案在结构上跟上面的很相似, 但是它没有使用仿制结点。替代方案是, 它维护了一个结构体 node** 指针, lastPtrRef 这指针总是指向结果链表的最后一个结点。这个方式解决了仿制结点解决的问题 — 处理了结果链表为空的情况。

struct Node* SortedMerge(struct Node* a, struct Node* b) 
{
  struct Node* result = NULL;

  /* point to the last result pointer */
  struct Node** lastPtrRef = &result; 

  while(1) 
  {
    if (a == NULL) 
    {
      *lastPtrRef = b;
       break;
    }
    else if (b==NULL) 
    {
       *lastPtrRef = a;
       break;
    }
    if(a->data <= b->data) 
    {
      MoveNode(lastPtrRef, &a);
    }
    else
    {
      MoveNode(lastPtrRef, &b);
    }

    /* tricky: advance to point to the next ".next" field */
    lastPtrRef = &((*lastPtrRef)->next); 
  }
  return(result);
}

方法 3 (使用递归)

递归的代码比迭代代码更加清晰。你可能不太想使用递归方法,因为它会随着链表的长度成比例的使用栈空间。

struct Node* SortedMerge(struct Node* a, struct Node* b) 
{
  struct Node* result = NULL;

  /* Base cases */
  if (a == NULL) 
     return(b);
  else if (b==NULL) 
     return(a);

  /* Pick either a or b, and recur */
  if (a->data <= b->data) 
  {
     result = a;
     result->next = SortedMerge(a->next, b);
  }
  else
  {
     result = b;
     result->next = SortedMerge(a, b->next);
  }
  return(result);
}

链表 | 第10课(查找链表中的循环)

发表于 2018-03-20

指定一个链表, 判断其中是否存在循环。下面的图表表示链表中存在一个循环。

下面用两种不同方法来实现

使用哈希:

顺序遍历链表,把结点地址放在一个哈希表中。任何时候, 如果地址为 NULL 表示链表到头,就返回 false, 如果当前结点的后继结点地址跟哈希表里的某个地址相同就返回 true.

// C++ program to detect loop in a linked list
#include<bits/stdc++.h>
using namespace std;

/* Link list node */
struct Node
{
    int data;
    struct Node* next;
};

void push(struct Node** head_ref, int new_data)
{
    /* allocate node */
    struct Node* new_node = new Node;

    /* put in the data  */
    new_node->data  = new_data;

    /* link the old list off the new node */
    new_node->next = (*head_ref);

    /* move the head to point to the new node */
    (*head_ref)    = new_node;
}

// Returns true if there is a loop in linked list
// else returns false.
bool detectLoop(struct Node *h)
{
    unordered_set<Node *> s;
    while (h != NULL)
    {
        // If we have already has this node
        // in hashmap it means their is a cycle
        // (Because you we encountering the
        // node second time).
        if (s.find(h) != s.end())
            return true;

        // If we are seeing the node for
        // the first time, insert it in hash
        s.insert(h);

        h = h->next;
    }

    return false;
}

/* Drier program to test above function*/
int main()
{
    /* Start with the empty list */
    struct Node* head = NULL;

    push(&head, 20);
    push(&head, 4);
    push(&head, 15);
    push(&head, 10);

    /* Create a loop for testing */
    head->next->next->next->next = head;

    if (detectLoop(head))
        cout << "Loop found";
    else
        cout << "No Loop";

    return 0;
}

输出 :
Loop Found

标记访问过的结点:

这个方案需要修改链表的数据结构。每个结点有个 visited 标记。遍历整个链表,标记所有访问过的结点。如果你再次看见访问过的结点,就表明存在一个循环。这个方案的时间复杂度是 O(n), 但是每个结点需要额外的信息。
这个方案可以修改成不修改链表的数据结构, 然后使用哈希来实现。仅仅是把访问过的结点地址放进哈希表,如果发现后面访问的地址在哈希表里存在,就表示有循环存在。

循环查找算法:

这是个最快的方法。使用两个指针进行遍历。一个指针每次向前移动一步,另外一个指针移动两步。如果这两个指针在某个结点相遇,就表明存在一个循环。如果指针不相遇就表明链表不存在循环。

循环查找算法的实现:

// C program to detect loop in a linked list
#include<stdio.h>
#include<stdlib.h>

/* Link list node */
struct Node
{
    int data;
    struct Node* next;
};

void push(struct Node** head_ref, int new_data)
{
    /* allocate node */
    struct Node* new_node =
          (struct Node*) malloc(sizeof(struct Node));

    /* put in the data  */
    new_node->data  = new_data;

    /* link the old list off the new node */
    new_node->next = (*head_ref);

    /* move the head to point to the new node */
    (*head_ref)    = new_node;
}

int detectloop(struct Node *list)
{
    struct Node  *slow_p = list, *fast_p = list;

    while (slow_p && fast_p && fast_p->next )
    {
        slow_p = slow_p->next;
        fast_p  = fast_p->next->next;
        if (slow_p == fast_p)
        {
           printf("Found Loop");
           return 1;
        }
    }
    return 0;
}

/* Drier program to test above function*/
int main()
{
    /* Start with the empty list */
    struct Node* head = NULL;

    push(&head, 20);
    push(&head, 4);
    push(&head, 15);
    push(&head, 10);

    /* Create a loop for testing */
    head->next->next->next->next = head;
    detectloop(head);

    return 0;
}


输出:
Found loop
时间复杂度: O(n)
空间复杂度: O(1)

链表 | 第9课(翻转链表)

发表于 2018-03-12

给定一个链表的头结点指针, 目标是翻转这个链表。

例子:

Input : Head of following linked list  
       1->2->3->4->NULL
Output : Linked list should be changed to,
       4->3->2->1->NULL

Input : Head of following linked list  
       1->2->3->4->5->NULL
Output : Linked list should be changed to,
       5->4->3->2->1->NULL

Input : NULL
Output : NULL

Input  : 1->NULL
Output : 1->NULL

迭代方法

利用3个指针,prev 为 NULL, curr 等于头结点,next 为 NULL.
遍历整个链表。在循环体内, 这样做。
// Before changing next of current,
// next node
next = curr->next

// This is where actual reversing happens
curr->next = prev

// Move prev and curr one step forward
prev = curr
curr = next

循环第一步结束, i.e., 在 next = curr->next 之后

循环一次之后

// Iterative C program to reverse a linked list
#include<stdio.h>
#include<stdlib.h>

/* Link list node */
struct Node
{
    int data;
    struct Node* next;
};

/* Function to reverse the linked list */
static void reverse(struct Node** head_ref)
{
    struct Node* prev   = NULL;
    struct Node* current = *head_ref;
    struct Node* next = NULL;
    while (current != NULL)
    {
        next  = current->next;  
        current->next = prev;   
        prev = current;
        current = next;
    }
    *head_ref = prev;
}

/* Function to push a node */
void push(struct Node** head_ref, int new_data)
{
    /* allocate node */
    struct Node* new_node =
            (struct Node*) malloc(sizeof(struct Node));

    /* put in the data  */
    new_node->data  = new_data;

    /* link the old list off the new node */
    new_node->next = (*head_ref);    

    /* move the head to point to the new node */
    (*head_ref)    = new_node;
}

/* Function to print linked list */
void printList(struct Node *head)
{
    struct Node *temp = head;
    while(temp != NULL)
    {
        printf("%d  ", temp->data);    
        temp = temp->next;  
    }
}    

/* Driver program to test above function*/
int main()
{
    /* Start with the empty list */
    struct Node* head = NULL;

     push(&head, 20);
     push(&head, 4);
     push(&head, 15); 
     push(&head, 85);      

     printf("Given linked list\n");
     printList(head);    
     reverse(&head);                      
     printf("\nReversed Linked list \n");
     printList(head);    
     getchar();
}

###
所给链表
85 15 4 20
翻转后的链表
20 4 15 85

时间复杂度: O(n)
空间复杂度: O(1)

递归方法:

1) 把链表划分为2部分 - 第一个结点和剩下的结点
2) 对剩下的链表调用翻转方法.
3) 把剩下的链表链接到第一个结点.
4) 固定头指针

void recursiveReverse(struct Node** head_ref)
{
    struct Node* first;
    struct Node* rest;

    /* empty list */
    if (*head_ref == NULL)
       return;   

    /* suppose first = {1, 2, 3}, rest = {2, 3} */
    first = *head_ref;  
    rest  = first->next;

    /* List has only one node */
    if (rest == NULL)
       return;   

    /* reverse the rest list and put the first element at the end */
    recursiveReverse(&rest);
    first->next->next  = first;  

    /* tricky step -- see the diagram */
    first->next  = NULL;          

    /* fix the head pointer */
    *head_ref = rest;              
}
时间复杂度: O(n)
空间复杂度: O(1)

一个更简单的方法

下面的方法用 C++ 实现。

// A simple and tail recursive C++ program to reverse
// a linked list
#include<bits/stdc++.h>
using namespace std;

struct Node
{
    int data;
    struct Node *next;
};

void reverseUtil(Node *curr, Node *prev, Node **head);

// This function mainly calls reverseUtil()
// with prev as NULL
void reverse(Node **head)
{
    if (!head)
        return;
    reverseUtil(*head, NULL, head);
}

// A simple and tail recursive function to reverse
// a linked list.  prev is passed as NULL initially.
void reverseUtil(Node *curr, Node *prev, Node **head)
{
    /* If last node mark it head*/
    if (!curr->next)
    {
        *head = curr;

        /* Update next to prev node */
        curr->next = prev;
        return;
    }

    /* Save curr->next node for recursive call */
    node *next = curr->next;

    /* and update next ..*/
    curr->next = prev;

    reverseUtil(next, curr, head);
}

// A utility function to create a new node
Node *newNode(int key)
{
    Node *temp = new Node;
    temp->data = key;
    temp->next = NULL;
    return temp;
}

// A utility function to print a linked list
void printlist(Node *head)
{
    while(head != NULL)
    {
        cout << head->data << " ";
        head = head->next;
    }
    cout << endl;
}

// Driver program to test above functions
int main()
{
    Node *head1 = newNode(1);
    head1->next = newNode(2);
    head1->next->next = newNode(3);
    head1->next->next->next = newNode(4);
    head1->next->next->next->next = newNode(5);
    head1->next->next->next->next->next = newNode(6);
    head1->next->next->next->next->next->next = newNode(7);
    head1->next->next->next->next->next->next->next = newNode(8);
    cout << "Given linked list\n";
    printlist(head1);
    reverse(&head1);
    cout << "\nReversed linked list\n";
    printlist(head1);
    return 0;
}

###

Output:
Given linked list
1 2 3 4 5 6 7 8

Reversed linked list
8 7 6 5 4 3 2 1

链表 | 第8课(输出链表的中间结点值)

发表于 2018-03-12

给定一个单链表, 查找中间结点。比如, 如果所给的链表是1->2->3->4->5就输出3.

如果有偶数个结点, 那就有两个中间的结点, 我们需要打印第二个结点元素。例如, 如果所给的链表是1->2->3->4->5->6那么就输出4

方法 1:

遍历整个链表并计算结点的个数。然后遍历结点到 count/2 然后返回此处的结点。

方法 2:

使用两个指针遍历链表。第一个指针每次移动一个位置,第二个指针每次移动两个位置。当最快的指针到达最后的时候,最慢的指针正好到达链表的中间位置。

#include<stdio.h>
#include<stdlib.h>

/* Link list node */
struct Node
{
    int data;
    struct Node* next;
};

/* Function to get the middle of the linked list*/
void printMiddle(struct Node *head)
{
    struct Node *slow_ptr = head;
    struct Node *fast_ptr = head;

    if (head!=NULL)
    {
        while (fast_ptr != NULL && fast_ptr->next != NULL)
        {
            fast_ptr = fast_ptr->next->next;
            slow_ptr = slow_ptr->next;
        }
        printf("The middle element is [%d]\n\n", slow_ptr->data);
    }
}

void push(struct Node** head_ref, int new_data)
{
    /* allocate node */
    struct Node* new_node =
        (struct Node*) malloc(sizeof(struct Node));

    /* put in the data  */
    new_node->data  = new_data;

    /* link the old list off the new node */
    new_node->next = (*head_ref);

    /* move the head to point to the new node */
    (*head_ref)    = new_node;
}

// A utility function to print a given linked list
void printList(struct Node *ptr)
{
    while (ptr != NULL)
    {
        printf("%d->", ptr->data);
        ptr = ptr->next;
    }
    printf("NULL\n");
}

/* Drier program to test above function*/
int main()
{
    /* Start with the empty list */
    struct Node* head = NULL;
    int i;

    for (i=5; i>0; i--)
    {
        push(&head, i);
        printList(head);
        printMiddle(head);
    }

    return 0;
}

Output:
5->NULL
The middle element is [5]

4->5->NULL
The middle element is [5]

3->4->5->NULL
The middle element is [4]

2->3->4->5->NULL
The middle element is [4]

1->2->3->4->5->NULL
The middle element is [3]

方法 3:

初始化一个中间元素等于头结点,然后初始化一个计数器为 0. 从头遍历链表, 遍历的时候增加计数器,当计时器是奇数时,修改mid为mid->next。

#include<stdio.h>
#include<stdlib.h>

/* Link list node */
struct node
{
    int data;
    struct node* next;
};

/* Function to get the middle of the linked list*/
void printMiddle(struct node *head)
{
    int count = 0;
    struct node *mid = head;

    while (head != NULL)
    {
        /* update mid, when 'count' is odd number */
        if (count & 1)
            mid = mid->next;

        ++count;
        head = head->next;
    }

    /* if empty list is provided */
    if (mid != NULL)
        printf("The middle element is [%d]\n\n", mid->data);
}


void push(struct node** head_ref, int new_data)
{
    /* allocate node */
    struct node* new_node =
        (struct node*) malloc(sizeof(struct node));

    /* put in the data  */
    new_node->data  = new_data;

    /* link the old list off the new node */
    new_node->next = (*head_ref);

    /* move the head to point to the new node */
    (*head_ref)    = new_node;
}

// A utility function to print a given linked list
void printList(struct node *ptr)
{
    while (ptr != NULL)
    {
        printf("%d->", ptr->data);
        ptr = ptr->next;
    }
    printf("NULL\n");
}

/* Drier program to test above function*/
int main()
{
    /* Start with the empty list */
    struct node* head = NULL;
    int i;

    for (i=5; i>0; i--)
    {
        push(&head, i);
        printList(head);
        printMiddle(head);
    }

    return 0;
}

Output:

5->NULL
The middle element is [5]

4->5->NULL
The middle element is [5]

3->4->5->NULL
The middle element is [4]

2->3->4->5->NULL
The middle element is [4]

1->2->3->4->5->NULL
The middle element is [3]

链表 | 第7课(获取第N个结点)

发表于 2018-03-12

编写一个函数 GetNth(),输入参数是一个链表和一个索引值,然后这个索引位置的结点值。

例如:

Input:  1->10->30->14,  index = 2
Output: 30  
The node at index 2 is 30

算法:

1. 初始化 count = 0
2. 循环链表
     a. 如果 count 等于输入的索引,就返回当前结点
         node
     b. 递增 count
     c. 当前指针后移

实现:

// C program to find n'th node in linked list
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

/* Link list node */
struct Node
{
    int data;
    struct Node* next;
};

/* Given a reference (pointer to pointer) to the head
    of a list and an int, push a new node on the front
    of the list. */
void push(struct Node** head_ref, int new_data)
{
    /* allocate node */
    struct Node* new_node =
            (struct Node*) malloc(sizeof(struct Node));

    /* put in the data  */
    new_node->data  = new_data;

    /* link the old list off the new node */
    new_node->next = (*head_ref);

    /* move the head to point to the new node */
    (*head_ref)    = new_node;
}

/* Takes head pointer of the linked list and index
    as arguments and return data at index*/
int GetNth(struct Node* head, int index)
{
    struct Node* current = head;
    int count = 0; /* the index of the node we're currently
                  looking at */
    while (current != NULL)
    {
       if (count == index)
          return(current->data);
       count++;
       current = current->next;
    }

    /* if we get to this line, the caller was asking
       for a non-existent element so we assert fail */
    assert(0);              
}

/* Drier program to test above function*/
int main()
{
    /* Start with the empty list */
    struct Node* head = NULL;

    /* Use push() to construct below list
     1->12->1->4->1  */
    push(&head, 1);
    push(&head, 4);
    push(&head, 1);
    push(&head, 12);
    push(&head, 1);  

    /* Check the count function */
    printf("Element at index 3 is %d", GetNth(head, 3));  
    getchar();
}

Output:
Element at index 3 is 4

时间复杂度: O(n)

方法 2- 使用递归

算法:

getnth(node,n)
1. 初始化 count = 1
2. if count == n
     return node->data
3. else
    return getnth(node->next,n-1)

实现:

// C program to find n'th node in linked list 
// using recursion
#include <bits/stdc++.h>
using namespace std;

/* Link list node */
struct Node
{
    int data;
    struct Node* next;
};

/*  Given a reference (pointer to pointer) to 
    the head of a list and an int, push a 
    new node on the front of the list. */
void push(struct Node** head_ref, int new_data)
{
    /* allocate node */
    struct Node* new_node =
            (struct Node*) malloc(sizeof(struct Node));

    /* put in the data */
    new_node->data = new_data;

    /* link the old list off the new node */
    new_node->next = (*head_ref);

    /* move the head to point to the new node */
    (*head_ref) = new_node;
}

/* Takes head pointer of the linked list and index
    as arguments and return data at index*/
int GetNth(struct Node *head,int n)
{
    int count = 1;

    //if count equal too n return node->data
    if(count == n)
    return head->data;

    //recursively decrease n and increase 
    // head to next pointer 
    return GetNth(head->next, n-1); 
}

/* Drier program to test above function*/
int main()
{
     /* Start with the empty list */
    struct Node* head = NULL;

    /* Use push() to construct below list
     1->12->1->4->1  */
    push(&head, 1);
    push(&head, 4);
    push(&head, 1);
    push(&head, 12);
    push(&head, 1);  

    /* Check the count function */
    printf("Element at index 3 is %d", GetNth(head, 3));  
    getchar();
}

Output:
Element at index 3 is 1

时间复杂度: O(n)

链表 | 第6课(交换结点而不交换数据)

发表于 2018-03-09

提供一个链表和两个key, 交换这两个key的结点。结点可以通过修改连接进行交换。而交换数据通常代价会很大, 因为数据可能含有很多内容。

假设链表里的所有key都是不同的。

例子:

Input:  10->15->12->13->20->14,  x = 12, y = 20
Output: 10->15->20->13->12->14

Input:  10->15->12->13->20->14,  x = 10, y = 20
Output: 20->15->12->13->10->14

Input:  10->15->12->13->20->14,  x = 12, y = 13
Output: 10->15->13->12->20->14

这个看起来很简单, 但是下面的场景处理起来很有趣。

  1. x 和 y 可能相邻也可能不是。
  2. x 和 y 可能是头结点。
  3. x 和 y 可能是尾结点。
  4. x 和/或 y 可能不在链表里。

如何写代码来处理上述所有的情况?

想法是,先在链表里搜索 x 和 y. 如果有一个没有找到, 就返回。在搜索 x 和 y 的时候, 跟踪当前和前驱指针。首先改变前驱指针的后继, 然后修改当前指针的后继。下面是 C 语言实现的方法。

#include<stdio.h>
#include<stdlib.h>

/* 链表结点 */
struct Node
{
    int data;
    struct Node *next;
};

void swapNodes(struct Node **head_ref, int x, int y)
{
   // 如果x 和 y 相等,直接返回
   if (x == y) return;

   // 搜索 x (跟踪 prevX 和 CurrX
   struct Node *prevX = NULL, *currX = *head_ref;
   while (currX && currX->data != x)
   {
       prevX = currX;
       currX = currX->next;
   }

   // 搜索 y (跟踪 prevY 和 CurrY
   struct Node *prevY = NULL, *currY = *head_ref;
   while (currY && currY->data != y)
   {
       prevY = currY;
       currY = currY->next;
   }

   // 如果 x 或者 y 没有找到, 返回
   if (currX == NULL || currY == NULL)
       return;

   // 如果 x 不是头结点
   if (prevX != NULL)
       prevX->next = currY;
   else // 否则 y 变成新的头结点
       *head_ref = currY;  

   // 如果 y 不是头结点
   if (prevY != NULL)
       prevY->next = currX;
   else  // 否则 x 变成新的头结点
       *head_ref = currX;

   // 交换后继指针
   struct Node *temp = currY->next;
   currY->next = currX->next;
   currX->next  = temp;
}

void push(struct Node** head_ref, int new_data)
{
    /* 分配结点 */
    struct Node* new_node =
        (struct Node*) malloc(sizeof(struct Node));

    /* 指定数据  */
    new_node->data  = new_data;

    /* 把新结点和老的链表相连 */
    new_node->next = (*head_ref);

    /* 新结点变成头结点 */
    (*head_ref)    = new_node;
}

/* 打印链表结点 */
void printList(struct Node *node)
{
    while(node != NULL)
    {
        printf("%d ", node->data);
        node = node->next;
    }
}

/* 测试函数 */
int main()
{
    struct Node *start = NULL;

    /* 构造的链表:
     1->2->3->4->5->6->7 */
    push(&start, 7);
    push(&start, 6);
    push(&start, 5);
    push(&start, 4);
    push(&start, 3);
    push(&start, 2);
    push(&start, 1);

    printf("\n Linked list before calling swapNodes() ");
    printList(start);

    swapNodes(&start, 4, 3);

    printf("\n Linked list after calling swapNodes() ");
    printList(start);

    return 0;
}


输出:
 Linked list before calling swapNodes() 1 2 3 4 5 6 7
 Linked list after calling swapNodes() 1 2 4 3 5 6 7

优化: 上面的代码可以优化,x 和 y 单独遍历。两个循环看起来比较简单。

更简单的方法

#include <iostream>

using namespace std;

// 结点类
class Node {

public:
    int data;
    class Node* next;

    // 构造器
    Node(int val, Node* next)
        : data(val), next(next)
    {
    }

    // 打印链表
    void printList()
    {

        Node* node = this;

        while (node != NULL) {

            cout << node->data;
            node = node->next;
        }

        cout << endl;
    }
};


void push(Node** head_ref, int new_data)
{

    // 分配结点
    (*head_ref) = new Node(new_data, *head_ref);
}

void swap(Node*& a, Node*& b)
{

    Node* temp = a;
    a = b;
    b = temp;
}

void swapNodes(Node** head_ref, int x, int y)
{

    // 如果 x 和 y 相等,返回
    if (x == y)
        return;

    Node **a = NULL, **b = NULL;

    // 在链表里搜索x 和 y
    // 然后保存它们的指针为 a 和 b
    while (*head_ref) {

        if ((*head_ref)->data == x) {
            a = head_ref;
        }

        else if ((*head_ref)->data == y) {
            b = head_ref;
        }

        head_ref = &((*head_ref)->next);
    }

    // 如果发现 a 和 b
    // 交换当前指针以及它们的后继指针
    if (a && b) {

        swap(*a, *b);
        swap(((*a)->next), ((*b)->next));
    }
}

int main()
{

    Node* start = NULL;

    // 构造链表:
    // 1->2->3->4->5->6->7
    push(&start, 7);
    push(&start, 6);
    push(&start, 5);
    push(&start, 4);
    push(&start, 3);
    push(&start, 2);
    push(&start, 1);

    cout << "Linked list before calling swapNodes() ";
    start->printList();

    swapNodes(&start, 6, 3);

    cout << "Linked list after calling swapNodes() ";
    start->printList();
}

输出:

 Linked list before calling swapNodes() 1 2 3 4 5 6 7
 Linked list after calling swapNodes() 1 2 6 4 5 3 7

链表 | 第5课(查找元素)

发表于 2018-03-09

写一个 C 函数,在指定的单链表中查找一个指定的 key ‘x’. 如果 x 存在返回 true 否则返回 false.

bool search(Node *head, int x) 

例如, 如果 key 是 15 ,链表是 14->21->11->30->10, 那么函数会返回 false. 如果 key 是 14, 函数就返回 true.

迭代方案

2) 初始一个结点指针, current = head.
3) 如果 current 不为 NULL 就继续循环
    a) current->key 等于要搜索的 key 返回 true.
    b) current = current->next
4) 返回 false 

下面是迭代方案的算法。

#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h

/* 链表结点 */
struct Node
{
    int key;
    struct Node* next;
};

/* 指向头结点的指针和一个整数, 在链表头部加入一个新结点. */
void push(struct Node** head_ref, int new_key)
{
    /* 分配结点 */
    struct Node* new_node =
            (struct Node*) malloc(sizeof(struct Node));

    /* 指定 key  */
    new_node->key  = new_key;

    /* 把新结点连接到老链表 */
    new_node->next = (*head_ref);

    /* 新结点变成头结点 */
    (*head_ref)    = new_node;
}

/* 判断 x 是否在链表里 */
bool search(struct Node* head, int x)
{
    struct Node* current = head;  // 初始 current
    while (current != NULL)
    {
        if (current->key == x)
            return true;
        current = current->next;
    }
    return false;
}

/* 测试程序*/
int main()
{
    /* Start with the empty list */
    struct Node* head = NULL;
    int x = 21;

    /* Use push() to construct below list
     14->21->11->30->10  */
    push(&head, 10);
    push(&head, 30);
    push(&head, 11);
    push(&head, 21);
    push(&head, 14);

    search(head, 21)? printf("Yes") : printf("No");
    return 0;
}

输出:
Yes

递归方案

bool search(head, x)
1) 如果头结点为 NULL, 返回 false.
2) 如果头结点的 key 等于 x, 返回 true;
2) 否则返回 search(head->next, x) 

下面是递归算法的实现.

#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
/* Link list node */
struct Node
{
    int key;
    struct Node* next;
};

/* 指向头结点的指针和一个整数, 在链表头部加入一个新结点. */
void push(struct Node** head_ref, int new_key)
{
    /* 分配结点 */
    struct Node* new_node =
            (struct Node*) malloc(sizeof(struct Node));

    /* 指定 key  */
    new_node->key  = new_key;

    /* 把新结点连接到老链表 */
    new_node->next = (*head_ref);

    /* 新结点变成头结点 */
    (*head_ref)    = new_node;
}

/* 判断 x 是否在链表里 */
bool search(struct Node* head, int x)
{
    // 递归结束条件
    if (head == NULL)
        return false;

    // 如果 key 在头结点, 返回 true
    if (head->key == x)
        return true;

    // 递归剩下的列表
    return search(head->next, x);
}

/* 测试程序*/
int main()
{
    /* 开始空链表 */
    struct Node* head = NULL;
    int x = 21;

    /* 使用 push() 构造链表
     14->21->11->30->10  */
    push(&head, 10);
    push(&head, 30);
    push(&head, 11);
    push(&head, 21);
    push(&head, 14);

    search(head, 21)? printf("Yes") : printf("No");
    return 0;
}

输出:
Yes

链表 | 第4课(链表的长度)

发表于 2018-03-09

要求写一个 C 函数计算指定单链表的结点数。

例如, 链表1->3->1->2->1通过这个函数可以获取到 5 个结点。

迭代方案

  1. 初始化计数为 0
  2. 初始化一个结点指针, current = head.
  3. 如果 current 不为 NULL 继续循环

    1
    2
    a) current = current -> next
    b) count++;
  1. 返回计数

下面是 C/C++ 实现的计数算法。

#include<stdio.h>
#include<stdlib.h>

/* Link list node */
struct Node
{
    int data;
    struct Node* next;
};

/* 指向头结点的指针和一个整数, 在链表的头部添加一个新结点. */
void push(struct Node** head_ref, int new_data)
{
    /* allocate node */
    struct Node* new_node =
            (struct Node*) malloc(sizeof(struct Node));

    /* put in the data  */
    new_node->data  = new_data;

    /* link the old list off the new node */
    new_node->next = (*head_ref);

    /* move the head to point to the new node */
    (*head_ref)    = new_node;
}

/* 获取链表的结点数 */
int getCount(struct Node* head)
{
    int count = 0;  // 初始值
    struct Node* current = head;  // 当前结点
    while (current != NULL)
    {
        count++;
        current = current->next;
    }
    return count;
}

/* 测试函数*/
int main()
{
    /* 开始空链表 */
    struct Node* head = NULL;

    /* 使用 push() 构造链表
     1->2->1->3->1  */
    push(&head, 1);
    push(&head, 3);
    push(&head, 1);
    push(&head, 2);
    push(&head, 1);

    /* 计数函数 */
    printf("count of nodes is %d", getCount(head));
    return 0;
}

输出:
count of nodes is 5

递归方案

int getCount(head)
  1. 如果头结点为 NULL, 返回 0.
  2. 否则返回 return 1 + getCount(head->next)

下面是 C/C++ 实现的递归算法。

#include<stdio.h>
#include<stdlib.h>

/* 链表结点 */
struct Node
{
    int data;
    struct Node* next;
};

/* 指向头结点的指针和一个整数, 在链表的头部添加一个新结点 */
void push(struct Node** head_ref, int new_data)
{
    /* 分配结点 */
    struct Node* new_node =
            (struct Node*) malloc(sizeof(struct Node));

    /* 指定数据  */
    new_node->data  = new_data;

    /* 指定新结点的后继为旧的头结点 */
    new_node->next = (*head_ref);

    /* 新结点成为头结点 */
    (*head_ref)    = new_node;
}

/* 计数函数*/
int getCount(struct Node* head)
{
    // 递归结束条件
    if (head == NULL)
        return 0;

    return 1 + getCount(head->next);
}

/* 测试函数*/
int main()
{
    /* 开始空链表 */
    struct Node* head = NULL;

    /* 使用 push() 构造链表
     1->2->1->3->1  */
    push(&head, 1);
    push(&head, 3);
    push(&head, 1);
    push(&head, 2);
    push(&head, 1);

    /* 计数函数 */
    printf("count of nodes is %d", getCount(head));
    return 0;
}

输出:
count of nodes is 5

链表| 第2课(链表的插入)

发表于 2018-03-09

在先前的文章里,我们已经简单介绍了链表。我们也创建了一个带有3个结点的简单链表,然后讨论了一下如何遍历它们。

这篇文章涉及到的所有程序均使用下面的结点表示链表。

1
2
3
4
5
6
// 链表结点
struct Node
{
int data;
struct Node *next;
};

这篇文章里, 会讨论如何在链表里插入结点。一般来说,有3个方法来添加结点。

  1. 在链表的首部。
  2. 在一个给定的结点之后。
  3. 在链表的尾部。

在首部添加一个结点: (4步)

新结点要添加在给定链表的首部。新添加的结点成为新的头结点。例如, 如果给定的链表是10->15->20->25 我们要再首部添加一个5, 然后链表就会变成5->10->15->20->25. 我们使用一个函数push()来表示。 这个函数必须接收一个指向头指针的指针, 因为需要修改它指向新加的结点。

下面是在首部添加结点的4个步骤。

/* 引用链表的头结点,指定一个整数, 然后在链表首部添加一个新的结点。*/
void push(struct Node** head_ref, int new_data)
{
    /* 1. 分配结点 */
    struct Node* new_node = (struct Node*) malloc(sizeof(struct Node));

    /* 2. 指定数据  */
    new_node->data  = new_data;

    /* 3. 指定新结点的后继为 head_ref */
    new_node->next = (*head_ref);

    /* 4. 头部指向新结点 */
    (*head_ref)    = new_node;
}

这个函数的时间复杂度为 O(1),因为它做的是连续数量的工作。

在指定的结点后面添加结点: (5 个步骤)

指定一个结点的指针, 然后新结点添加在它的后面。

/* 指定一个 prev_node, 然后在它后面添加一个新的结点 */
void insertAfter(struct Node* prev_node, int new_data)
{
    /*1. 判断所给的结点是否为 NULL */
    if (prev_node == NULL) 
    { 
       printf("the given previous node cannot be NULL");       
       return;  
    }  

    /* 2. 分配新的结点 */
    struct Node* new_node =(struct Node*) malloc(sizeof(struct Node));

    /* 3. 指定数据  */
    new_node->data  = new_data;

    /* 4. 新结点的后继指定为 prev_node 的后继*/
    new_node->next = prev_node->next; 

    /* 5. 指定 prev_node 后继为新结点 */
    prev_node->next = new_node;
}

这个函数的时间复杂度为 O(1),因为它做的是连续数量的工作。

在链表尾部添加结点: (6 步)

新结点要求添加在链表的最后。例如,如果给定的链表是5->10->15->20->25然后在最后添加一个30, 然后链表就变成了5->10->15->20->25->30.

因为链表是用头结点表示的, 所以我们必须整个遍历它, 然后修改最后一个结点的后继为新的结点。

/* 引用链表的头结点, 指定一个整数, 在尾部添加一个新结点 */
void append(struct Node** head_ref, int new_data)
{
    /* 1. 分配结点 */
    struct Node* new_node = (struct Node*) malloc(sizeof(struct Node));

    struct Node *last = *head_ref;  /* used in step 5*/

    /* 2. 指定数据  */
    new_node->data  = new_data;

    /* 3. 新结点是最后一个结点,所以它的后继为 NULL*/
    new_node->next = NULL;

    /* 4. 如果链表为空, 那么新结点就是头结点 */
    if (*head_ref == NULL)
    {
       *head_ref = new_node;
       return;
    }  

    /* 5. 遍历到最后一个结点 */
    while (last->next != NULL)
        last = last->next;

    /* 6. 修改最后一个结点的后继为新结点 */
    last->next = new_node;
    return;    
}

这个函数的时间复杂度为 O(n) n 是链表的结点数。因为这里有一个循环。

这个函数可以优化到时间复杂度为 O(1),方法是使用一个尾部指针。

下面是一个完整的程序,使用上面所有的函数创建一个链表

#include <stdio.h>
#include <stdlib.h>

// 链表结点
struct Node
{
  int data;
  struct Node *next;
};

/* 指向头结点的指针和一个整数, 在链表前面插入一个新的结点. */
void push(struct Node** head_ref, int new_data)
{
    /* 1. 分配结点 */
    struct Node* new_node = (struct Node*) malloc(sizeof(struct Node));

    /* 2. 指定数据  */
    new_node->data  = new_data;

    /* 3. 新结点的后继作为头部 */
    new_node->next = (*head_ref);

    /* 4. 头部指向新结点 */
    (*head_ref)    = new_node;
}

/* 指定一个 prev_node, 在给定的结点后面插入一个新结点 */
void insertAfter(struct Node* prev_node, int new_data)
{
    /*1. 判断所给的结点是否为 NULL */
    if (prev_node == NULL)
    {
      printf("the given previous node cannot be NULL");
      return;
    }

    /* 2. 分配结点 */
    struct Node* new_node =(struct Node*) malloc(sizeof(struct Node));

    /* 3. 指定数据  */
    new_node->data  = new_data;

    /* 4. 指定新结点的后继为 prev_node 的后继 */
    new_node->next = prev_node->next;

    /* 5. 指定 prev_node 后继为新结点 */
    prev_node->next = new_node;
}

/* 指向头结点的指针和一个整数, 在链表的尾部添加一个新结点  */
void append(struct Node** head_ref, int new_data)
{
    /* 1. 分配结点 */
    struct Node* new_node = (struct Node*) malloc(sizeof(struct Node));

    struct Node *last = *head_ref;  /* used in step 5*/

    /* 2. 指定数据  */
    new_node->data  = new_data;

    /* 3. 新结点会成为最后一个结点, 所以它的后继为 NULL*/
    new_node->next = NULL;

    /* 4. 如果链表为空, 新结点就是头结点 */
    if (*head_ref == NULL)
    {
       *head_ref = new_node;
       return;
    }

    /* 5. 遍历到最后一个结点 */
    while (last->next != NULL)
        last = last->next;

    /* 6. 修改最后一个结点的后继为新结点 */
    last->next = new_node;
    return;
}

// 这个函数从头打印链表的内容
void printList(struct Node *node)
{
  while (node != NULL)
  {
     printf(" %d ", node->data);
     node = node->next;
  }
}

/* 测试函数*/
int main()
{
  /* 空链表开始 */
  struct Node* head = NULL;

  // 插入 6.  链表变成 6->NULL
  append(&head, 6);

  // 在首部插入 7 . 链表变成 7->6->NULL
  push(&head, 7);

  // 在首部插入 1. 链表变成 1->7->6->NULL
  push(&head, 1);

  // 在尾部插入4. 链表变成 1->7->6->4->NULL
  append(&head, 4);

  // 在 7 后面插入 8, 链表变成 1->7->8->6->4->NULL
  insertAfter(head->next, 8);

  printf("\n Created Linked list is: ");
  printList(head);

  return 0;
}

输出:
 Created Linked list is:  1  7  8  6  4

链表 |第 1 课(链表的介绍)

发表于 2018-03-09

跟数组类似, 链表也是一个线性的数据结构。跟数组不同的是, 链表的元素并不是相邻的; 而是通过指针联系在一起。

为什么会有链表?

数组可以用来存储相似类型的线性数据, 但是数组有以下的限制。

  1. 数组的大小是固定的: 所以我们必须知道元素数量的上限。同时, 分配的内存对应了上限的使用限制。
  2. 在数组中插入一个元素代价非常大, 因为要为新的元素创建空间, 还要移动已存在的元素。

例如, 如果有一个排序后的 ID 数组 id[].

id[] = [1000, 1010, 1050, 2000, 2040].

如果我们要插入一个新的元素 ID 1005, 然后还要排序, 我们需要移动所有位于 1000 (除了 1000)之后的元素。

对数组进行删除也很麻烦, 除非使用一些特殊技术。例如, 要删除 1010, 位于其后的所有元素都得移动。

相比数组的优点

  1. 动态大小
  2. 容易插入/删除

缺点:

  1. 不允许随机访问。我们必须从第一个结点开始顺序访问元素。所以我们不能对链表进行折半查找。
  2. 链表的每个元素都要额外的内存空间。

C语言表示法:

链表通常用指向头结点的指针表示。如果链表是空的, 头结点就是 NULL.

结点通常有两个部分组成:

  1. 数据
  2. 指向下一个结点的指针

在 C 语言中, 我们可以用结构图表示一个结点。下面的例子是一个带有整数的结点。

// 链表的结点
struct Node
{
  int data;
  struct Node *next;
};

下面,我们来创建一个含有 3 个结点的链表。

// 链表
#include<stdio.h>
#include<stdlib.h>

struct Node 
{
  int data;
  struct Node *next;
};

// 创建含有3个结点的链表
int main()
{
  struct Node* head = NULL;
  struct Node* second = NULL;
  struct Node* third = NULL;

  // 在堆里分配3个结点  
  head = (struct Node*)malloc(sizeof(struct Node)); 
  second = (struct Node*)malloc(sizeof(struct Node));
  third = (struct Node*)malloc(sizeof(struct Node));

  /* 动态分配了3块内存。 
     这3块内存分别是 first, second 喝 third     
       head           second           third
        |                |               |
        |                |               |
    +---+-----+     +----+----+     +----+----+
    | #  | #  |     | #  | #  |     |  # |  # |
    +---+-----+     +----+----+     +----+----+

   # 井号表示随机的值。
   数据是随机的,因为我们还没有制定 */

  head->data = 1; //指定第一个结点的数据
  head->next = second; // 把第一个结点和第二个结点连接起来

  /* 现在第一个结点有数据了,它的下一个结点是 second.

       head          second         third
        |              |              |
        |              |              |
    +---+---+     +----+----+     +-----+----+
    | 1  | o----->| #  | #  |     |  #  | #  |
    +---+---+     +----+----+     +-----+----+    
  */ 

  second->data = 2; //指定第二个结点的数据
  second->next = third; // 把第二个结点和第三个结点连接起来

  /* 现在第二个结点有数据了,他的下一个结点是 third.  

       head         second         third
        |             |             |
        |             |             |
    +---+---+     +---+---+     +----+----+
    | 1  | o----->| 2 | o-----> |  # |  # |
    +---+---+     +---+---+     +----+----+      */   

  third->data = 3; //指定第三个结点的数据
  third->next = NULL;

  /* 现在第三个结点也有数据了, 他的后继指针为 NULL 表示链表结束在这里.

           head    
             |
             | 
        +---+---+     +---+---+       +----+------+
        | 1  | o----->|  2  | o-----> |  3 | NULL |
        +---+---+     +---+---+       +----+------+       


    注意只有头结点可以完整的表示整个链表. */     

  return 0;
}

遍历链表

在上面的程序里, 我们创建了一个含有3个结点的链表。现在我们来遍历这个链表, 打印每个结点的数据。为了遍历方便, 我们写了一个函数 printList()来打印所给的链表。

我们强烈建议你在查看解决方案前,自己先练习一下。

// 遍历链表的 C 语言实现
#include<stdio.h>
#include<stdlib.h>

struct Node 
{
  int data;
  struct Node *next;
};

// 打印方法
void printList(struct Node *n)
{
  while (n != NULL)
  {
     printf(" %d ", n->data);
     n = n->next;
  }
}

int main()
{
  struct Node* head = NULL;
  struct Node* second = NULL;
  struct Node* third = NULL;

  // 在堆中分配3个结点  
  head  = (struct Node*)malloc(sizeof(struct Node)); 
  second = (struct Node*)malloc(sizeof(struct Node));
  third  = (struct Node*)malloc(sizeof(struct Node));

  head->data = 1; //指定第一个结点的数据
  head->next = second; // 连接到第二个结点   

  second->data = 2; //指定第二个结点的数据
  second->next = third;  

  third->data = 3; //指定第三个结点的数据
  third->next = NULL;

  printList(head);

  return 0;
}

Output:
 1  2  3
12

跳跳魔王

11 日志
RSS
© 2018 跳跳魔王 本站总访问量次
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4