数据结构:单向链表(Linked List)

Posted passedbylove

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据结构:单向链表(Linked List)相关的知识,希望对你有一定的参考价值。

本文来源:

Linked List | Set 1 (Introduction)

Linked List | Set 2 (Inserting a node)

Linked List | Set 3 (Deleting a node)

Find Length of a Linked List (Iterative and Recursive) (todo:在结构中保持节点数信息)

Search an element in a Linked List (Iterative and Recursive)

Pairwise swap adjacent nodes of a linked list by changing pointers | Set 2  todo:交换相邻两个节点

Program to reverse a linked list using Stack ,链表倒序重新组织

Binary Search on Singly Linked List

Find the middle of a given linked list in C and Java

 

、正序、逆序插入

 基础知识

介绍:链表与数组一样,同属于线性表的一个子集。不同之处在于链表元素并不需要存储到一块连续的内存空间;

链表中的元素通过指针来链接并维护各个节点之间的联系,可使用连续的内存空间、亦可不使用连续的内存空间。

技术图片

 

使用链表的原因:

1、数组类型长度是固定的,一旦申明不可以修改长度。在实际使用中我们必须事先知道元素数量的上限。

2、实际使用过程中分配的数组上限的内存空间,而事实不一定占用那么多空间。

3、插入删除成本高,必须要进行位移。插入平均位移n/2个元素,删除移动(n-1)/2个元素。最理想的时间成本

    O(1)即被删除/插入元素后面没有元素。

 

链表有点:

1、动态分配内存,即用即申请,占用空间大小也是动态的。

2、插入、删除方便,特别是在头和尾,修改指针地址即可。

缺点:

1、不可以随机访问,必须遍历。二分查找时间复杂度为O(n),数组二分查找时间复杂度O(log2n) (注:O(n)> O(log2n))

2、列表中每个节点还需要额外的内存空间存储指针域(链)数据

3、数组中元素占用的存储空间是连续的,可以根据索引进行访问元素,链表不支持此操作。

 

 

数据展现:

链表第一个节点叫头节点。如果链表为空,数据域、指针域(链、引用)都是NULL

 

c语言中通常用结构展示数据,在java或者C#中用类和另外一个节点类(Node)来表示,LinkedList类包含Node类型的引用。

创建3个节点并遍历

c语言:

// A simple C program to introduce 
// a linked list 
#include <stdio.h> 
#include <stdlib.h> 

// A linked list node 
struct Node  
    int data; 
    struct Node* next; 
; 

// This function prints contents of linked list starting from 
// the given node 
void printList(struct Node* n) 
 
    while (n != NULL)  
        printf(" %d ", n->data); 
        n = n->next; 
     
 

// Program to create a simple linked 
// list with 3 nodes 
int main() 
 
    struct Node* head = NULL; 
    struct Node* second = NULL; 
    struct Node* third = NULL; 
  
    // allocate 3 nodes in the heap 
    head = (struct Node*)malloc(sizeof(struct Node)); 
    second = (struct Node*)malloc(sizeof(struct Node)); 
    third = (struct Node*)malloc(sizeof(struct Node)); 
  
    /* Three blocks have been allocated dynamically.  
     We have pointers to these three blocks as first, 
     second and third      
       head           second           third 
        |                |               | 
        |                |               | 
    +---+-----+     +----+----+     +----+----+ 
    | #  | #  |     | #  | #  |     |  # |  # | 
    +---+-----+     +----+----+     +----+----+ 
     
   # represents any random value. 
   Data is random because we haven’t assigned  
   anything yet  */
  
    head->data = 1; // assign data in first node 
    head->next = second; // Link first node with 
    // the second node 
  
    /* data has been assigned to the data part of the first 
     block (block pointed by the head). And next 
     pointer of first block points to second.   
     So they both are linked. 
  
       head          second         third 
        |              |              | 
        |              |              | 
    +---+---+     +----+----+     +-----+----+ 
    | 1  | o----->| #  | #  |     |  #  | #  | 
    +---+---+     +----+----+     +-----+----+     
  */
  
    // assign data to second node 
    second->data = 2; 
  
    // Link second node with the third node 
    second->next = third; 
  
    /* data has been assigned to the data part of the second 
     block (block pointed by second). And next 
     pointer of the second block points to the third  
     block. So all three blocks are linked. 
    
       head         second         third 
        |             |             | 
        |             |             | 
    +---+---+     +---+---+     +----+----+ 
    | 1  | o----->| 2 | o-----> |  # |  # | 
    +---+---+     +---+---+     +----+----+      */
  
    third->data = 3; // assign data to third node 
    third->next = NULL; 
  
    /* data has been assigned to data part of third 
    block (block pointed by third). And next pointer 
    of the third block is made NULL to indicate 
    that the linked list is terminated here. 
  
     We have the linked list ready.   
  
           head     
             | 
             |  
        +---+---+     +---+---+       +----+------+ 
        | 1  | o----->| 2 |   o-----> |  3 | NULL | 
        +---+---+     +---+---+       +----+------+        
     
      
    Note that only head is sufficient to represent  
    the whole list.  We can traverse the complete  
    list by following next pointers.    */
  
  
  printList(head); 
    return 0; 
 

输出结果:

1 2 3

java:

// A simple Java program to introduce a linked list 
public class LinkedList  
    Node head; // head of list 
  
    /* Linked list Node.  This inner class is made static so that 
       main() can access it */
    static class Node  
        int data; 
        Node next; 
        Node(int d) 
         
            data = d; 
            next = null; 
         // Constructor 
    

/* This function prints contents of linked list starting from head */
    public void printList() 
     
        Node n = head; 
        while (n != null)  
            System.out.print(n.data + " "); 
            n = n.next; 
         
         
  
    /* method to create a simple linked list with 3 nodes*/
    public static void main(String[] args) 
     
        /* Start with the empty list. */
        LinkedList llist = new LinkedList(); 
  
        llist.head = new Node(1); 
        Node second = new Node(2); 
        Node third = new Node(3); 
  
        /* Three nodes have been allocated dynamically. 
          We have references to these three blocks as first,   
          second and third 
  
          llist.head        second              third 
             |                |                  | 
             |                |                  | 
         +----+------+     +----+------+     +----+------+ 
         | 1  | null |     | 2  | null |     |  3 | null | 
         +----+------+     +----+------+     +----+------+ */
  
        llist.head.next = second; // Link first node with the second node 
  
        /*  Now next of the first Node refers to the second.  So they 
            both are linked. 
  
         llist.head        second              third 
            |                |                  | 
            |                |                  | 
        +----+------+     +----+------+     +----+------+ 
        | 1  |  o-------->| 2  | null |     |  3 | null | 
        +----+------+     +----+------+     +----+------+ */
  
        second.next = third; // Link second node with the third node 
  
        /*  Now next of the second Node refers to third.  So all three 
            nodes are linked. 
  
         llist.head        second              third 
            |                |                  | 
            |                |                  | 
        +----+------+     +----+------+     +----+------+ 
        | 1  |  o-------->| 2  |  o-------->|  3 | null | 
        +----+------+     +----+------+     +----+------+ */
        
        llist.printList(); 
     

c#

using System; 
  
public class LinkedList  
    Node head; // head of list 
  
    /* Linked list Node. This inner class is made static so that  
    main() can access it */
    public class Node  
        public int data; 
        public Node next; 
        public Node(int d) 
         
            data = d; 
            next = null; 
         // Constructor 
     
    
      /* This function prints contents of  
    linked list starting from head */
    public void printList() 
     
        Node n = head; 
        while (n != null)  
            Console.Write(n.data + " "); 
            n = n.next; 
         
    
  
    /* method to create a simple linked list with 3 nodes*/
    public static void Main(String[] args) 
     
        /* Start with the empty list. */
        LinkedList llist = new LinkedList(); 
  
        llist.head = new Node(1); 
        Node second = new Node(2); 
        Node third = new Node(3); 
  
        /* Three nodes have been allocated dynamically.  
        We have references to these three blocks as first,  
        second and third  
  
        llist.head     second             third  
            |             |                 |  
            |             |                 |  
        +----+------+     +----+------+     +----+------+  
        | 1  | null |     | 2  | null |     | 3  | null |  
        +----+------+     +----+------+     +----+------+ */
  
        llist.head.next = second; // Link first node with the second node 
  
        /* Now next of first Node refers to second. So they  
            both are linked.  
  
        llist.head     second             third  
            |             |                 |  
            |             |                 |  
        +----+------+     +----+------+     +----+------+  
        | 1   | o-------->| 2  | null |     | 3  | null |  
        +----+------+     +----+------+     +----+------+ */
  
        second.next = third; // Link second node with the third node 
  
        /* Now next of the second Node refers to third. So all three  
            nodes are linked.  
  
        llist.head     second             third  
            |             |                 |  
            |             |                 |  
        +----+------+     +----+------+     +----+------+  
        | 1  | o-------->| 2   | o--------> | 3  | null |  
        +----+------+     +----+------+     +----+------+ */
        
        llist.printList(); 
     
 

 插入节点

在链表中插入节点有以下三种情形:

1、在链表头部

2、在特定节点

3、在链表尾部

1)在头部添加(4步操作)

新节点添加到头部,将成为新的头节点,以下将节点添加到链表头部的函数是push,push接收指向链表头部的指针,然后将指针修改

技术图片

指向新的节点:

c语言:

/* Given a reference (pointer to pointer) to the head of a list 
   and an int,  inserts a new node on the front of the list. */
void push(struct Node** head_ref, int new_data) 
 
    /* 1. allocate node */
    struct Node* new_node = (struct Node*) malloc(sizeof(struct Node)); 
   
    /* 2. put in the data  */
    new_node->data  = new_data; 
   
    /* 3. Make next of new node as head */
    new_node->next = (*head_ref); 
   
    /* 4. move the head to point to the new node */
    (*head_ref)    = new_node; 
 

Java:

/* This function is in LinkedList class. Inserts a 
   new Node at front of the list. This method is  
   defined inside LinkedList class shown above */
public void push(int new_data) 
 
    /* 1 & 2: Allocate the Node & 
              Put in the data*/
    Node new_node = new Node(new_data); 
  
    /* 3. Make next of new Node as head */
    new_node.next = head; 
  
    /* 4. Move the head to point to new Node */
    head = new_node; 
 

 

push时间复杂度O(1)

2)在特定节点(5步操作)

技术图片

c语言:

/* Given a node prev_node, insert a new node after the given 
   prev_node */
void insertAfter(struct Node* prev_node, int new_data) 
 
    /*1. check if the given prev_node is NULL */ 
    if (prev_node == NULL)  
      
       printf("the given previous node cannot be NULL");        
       return;   
       
           
    /* 2. allocate new node */
    struct Node* new_node =(struct Node*) malloc(sizeof(struct Node)); 
   
    /* 3. put in the data  */
    new_node->data  = new_data; 
   
    /* 4. Make next of new node as next of prev_node */
    new_node->next = prev_node->next;  
   
    /* 5. move the next of prev_node as new_node */
    prev_node->next = new_node; 

java:

/* This function is in LinkedList class. 
   Inserts a new node after the given prev_node. This method is  
   defined inside LinkedList class shown above */
public void insertAfter(Node prev_node, int new_data) 
 
    /* 1. Check if the given Node is null */
    if (prev_node == null) 
     
        System.out.println("The given previous node cannot be null"); 
        return; 
     
  
    /* 2. Allocate the Node & 
       3. Put in the data*/
    Node new_node = new Node(new_data); 
  
    /* 4. Make next of new Node as next of prev_node */
    new_node.next = prev_node.next; 
  
    /* 5. make next of prev_node as new_node */
    prev_node.next = new_node; 
 

因为已经给定了节点,所以insertAfter时间复杂为O(1)

3)在链表尾部(6个步骤)

我们需要遍历链表,得到最后一个节点,然后把新节点追加到最后,然后让他成为新的尾部节点

技术图片

c语言:

/* Given a reference (pointer to pointer) to the head 
   of a list and an int, appends a new node at the end  */
void append(struct Node** head_ref, int new_data) 
 
    /* 1. allocate node */
    struct Node* new_node = (struct Node*) malloc(sizeof(struct Node)); 
  
    struct Node *last = *head_ref;  /* used in step 5*/
   
    /* 2. put in the data  */
    new_node->data  = new_data; 
  
    /* 3. This new node is going to be the last node, so make next  
          of it as NULL*/
    new_node->next = NULL; 
  
    /* 4. If the Linked List is empty, then make the new node as head */
    if (*head_ref == NULL) 
     
       *head_ref = new_node; 
       return; 
       
       
    /* 5. Else traverse till the last node */
    while (last->next != NULL) 
        last = last->next; 
   
    /* 6. Change the next of last node */
    last->next = new_node; 
    return;     
 

java:

/* Appends a new node at the end.  This method is  
   defined inside LinkedList class shown above */
public void append(int new_data) 
 
    /* 1. Allocate the Node & 
       2. Put in the data 
       3. Set next as null */
    Node new_node = new Node(new_data); 
  
    /* 4. If the Linked List is empty, then make the 
           new node as head */
    if (head == null) 
     
        head = new Node(new_data); 
        return; 
     
  
    /* 4. This new node is going to be the last node, so 
         make next of it as null */
    new_node.next = null; 
  
    /* 5. Else traverse till the last node */
    Node last = head;  
    while (last.next != null) 
        last = last.next; 
  
    /* 6. Change the next of last node */
    last.next = new_node; 
    return; 
 

因为需要从头到尾循环,所以append时间复杂度为O(n),其中n是节点的个数。

当然你可以修改结构,保存尾节点,使其复杂度降为O(1)

 删除节点(3步操作)

删除步骤

1) 找到待删除节点的前驱

2) 修改前驱节点的指针域指向待删除节点的后继节点

3)释放待删除的节点的内存空间

//todo:没有c#的代码添加上去。修改结构,添加一个尾部节点地址的指针

技术图片

c语言实现:

因为链表中的每个节点都是使用malloc()动态创建的,所以需要调用free()释放待删除节点占用的内存空间。

// A complete working C program to demonstrate deletion in singly 
// linked list 
#include <stdio.h> 
#include <stdlib.h> 

// A linked list node 
struct Node 
 
    int data; 
    struct Node *next; 
; 

/* Given a reference (pointer to pointer) to the head of a list 
and an int, inserts a new node on the front of the list. */
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; 
 

/* Given a reference (pointer to pointer) to the head of a list 
and a key, deletes the first occurrence of key in linked list */
void deleteNode(struct Node **head_ref, int key) 
 
    // Store head node 
    struct Node* temp = *head_ref, *prev; 

    // If head node itself holds the key to be deleted 
    if (temp != NULL && temp->data == key) 
     
        *head_ref = temp->next; // Changed head 
        free(temp);             // free old head 
        return; 
     

    // Search for the key to be deleted, keep track of the 
    // previous node as we need to change ‘prev->next‘ 
    while (temp != NULL && temp->data != key) 
     
        prev = temp; 
        temp = temp->next; 
     

    // If key was not present in linked list 
    if (temp == NULL) return; 

    // Unlink the node from linked list 
    prev->next = temp->next; 

    free(temp); // Free memory 
 

// This function prints contents of linked list starting from 
// the given node 
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* head = NULL; 

    push(&head, 7); 
    push(&head, 1); 
    push(&head, 3); 
    push(&head, 2); 

    puts("Created Linked List: "); 
    printList(head); 
    deleteNode(&head, 1); 
    puts("\\nLinked List after Deletion of 1: "); 
    printList(head); 
    return 0; 

因为涉及到遍历,时间复杂度O(n)

输出

Created Linked List: 
 2  3  1  7 
Linked List after Deletion of 1: 
 2  3  7 

java:

// A complete working Java program to demonstrate deletion in singly 
// linked list 
class LinkedList 
 
    Node head; // head of list 
  
    /* Linked list Node*/
    class Node 
     
        int data; 
        Node next; 
        Node(int d) 
         
            data = d; 
            next = null; 
         
     
  
    /* Given a key, deletes the first occurrence of key in linked list */
    void deleteNode(int key) 
     
        // Store head node 
        Node temp = head, prev = null; 
  
        // If head node itself holds the key to be deleted 
        if (temp != null && temp.data == key) 
         
            head = temp.next; // Changed head 
            return; 
         
  
        // Search for the key to be deleted, keep track of the 
        // previous node as we need to change temp.next 
        while (temp != null && temp.data != key) 
         
            prev = temp; 
            temp = temp.next; 
             
  
        // If key was not present in linked list 
        if (temp == null) return; 
  
        // Unlink the node from linked list 
        prev.next = temp.next; 
     
  
    /* Inserts a new Node at front of the list. */
    public void push(int new_data) 
     
        Node new_node = new Node(new_data); 
        new_node.next = head; 
        head = new_node; 
     
  
    /* This function prints contents of linked list starting from 
        the given node */
    public void printList() 
     
        Node tnode = head; 
        while (tnode != null) 
         
            System.out.print(tnode.data+" "); 
            tnode = tnode.next; 
         
     
  
    /* Drier program to test above functions. Ideally this function 
    should be in a separate user class. It is kept here to keep 
    code compact */
    public static void main(String[] args) 
     
        LinkedList llist = new LinkedList(); 
  
        llist.push(7); 
        llist.push(1); 
        llist.push(3); 
        llist.push(2); 
  
        System.out.println("\\nCreated Linked list is:"); 
        llist.printList(); 
  
        llist.deleteNode(1); // Delete node at position 4 
  
        System.out.println("\\nLinked List after Deletion at position 4:"); 
        llist.printList(); 
     
 

 

获取链表长度(迭代法和递归法)

迭代法

1、设定一个计数器,初始值为0

2、初始化current到头节点

3、如果current不为null进行以下循环

     a) current = current -> next
     b) count++;

4、返回计数器

c语言:
/* Counts no. of nodes in linked list */
int getCount(struct Node* head) 
 
    int count = 0;  // Initialize count 
    struct Node* current = head;  // Initialize current 
    while (current != NULL) 
     
        count++; 
        current = current->next; 
     
    return count; 
 

java:

 /* Returns count of nodes in linked list */
    public int getCount() 
     
        Node temp = head; 
        int count = 0; 
        while (temp != null) 
         
            count++; 
            temp = temp.next; 
         
        return count; 
     

c#:

 /* Returns count of nodes in linked list */
    public int getCount() 
     
        Node temp = head; 
        int count = 0; 
        while (temp != null) 
         
            count++; 
            temp = temp.next; 
         
        return count; 
     

 递归法

int getCount(head)
1) If head is NULL, return 0.
2) Else return 1 + getCount(head->next) 
c语言:
/* Counts the no. of occurrences of a node 
   (search_for) in a linked list (head)*/
int getCount(struct Node* head) 
 
    // Base case 
    if (head == NULL) 
        return 0; 
  
    // count is 1 + count of remaining list 
    return 1 + getCount(head->next); 

java

  /* Returns count of nodes in linked list */
    public int getCountRec(Node node) 
     
        // Base case 
        if (node == null) 
            return 0; 
  
        // Count is this node plus rest of the list 
        return 1 + getCountRec(node.next); 
    

c#

 /* Returns count of nodes in linked list */
    public int getCountRec(Node node)  
      
        // Base case  
        if (node == null)  
            return 0;  
  
        // Count is this node plus rest of the list  
        return 1 + getCountRec(node.next);  
      

 

在链表中查找元素

函数签名:

bool search(Node *head, int x) 

如果在链表中查找到这个元素返回true,否则false

迭代法

2) 初始化一个节点指针, current = head.
3) 如果current不为NULL执行以下循环
    a) current->key 等于当前待查找值key则返回true.
    b) current = current->next
4) 否则返回 false 
/*Checks whether the value key is present in linked list*/
bool search(struct Node* head, int key)

    struct Node* current = head; //Initialize current

    while(current != NULL)
    
        if(current->data == key)
            return true;
        current = current->next;
    

    return false;

java:

  //Checks whether the value key is present in linked list 
    public boolean search(Node head, int key) 
     
        Node current = head;    //Initialize current 
        while (current != null) 
         
            if (current.data == key) 
                return true;    //data found 
            current = current.next; 
         
        return false;    //data not found 
    

c#

  // Checks whether the value key is present in linked list 
    public bool search(Node head, int key) 
     
        Node current = head; // Initialize current 
        while (current != null) 
         
            if (current.data == key) 
                return true; // data found 
            current = current.next; 
         
        return false; // data not found 
     

 

递归法:

bool search(head, x)
1)如果head是NULL, return false.
2) 如果head数据域的值和待查找的一致,返回true;
2) 否则返回 search(head->next, x) 

c语言:

/*
    Checks whether the value key is present in linked list
 */
bool searchRecursive(struct Node* head, int key)

    if(head == NULL)
    
        return false;
    

    //If key is present in current node,
    //return true
    if(head->data == key)
        return true;

    //recursive for remaining list
    return searchRecursive(head->next, key);

 java:

  // Checks whether the value key is present 
    // in linked list 
    public boolean search(Node head, int key) 
     
        // Base case 
        if (head == null) 
            return false; 
  
        // If key is present in current node, 
        // return true 
        if (head.data == key) 
            return true; 
  
        // Recur for remaining list 
        return search(head.next, key); 
     

c#

// Checks whether the value key is present 
    // in linked list 
    public bool search(Node head, int key) 
     
        // Base case 
        if (head == null) 
            return false; 
  
        // If key is present in current node, 
        // return true 
        if (head.data == key) 
            return true; 
  
        // Recur for remaining list 
        return search(head.next, key); 
     

 

以上是关于数据结构:单向链表(Linked List)的主要内容,如果未能解决你的问题,请参考以下文章

Linked List

Reverse Linked List(反转单向链表)

链表(Linked List): 单链表

链表(linked-list)

链表 (Linked List)

Leetcode 206 Reverse Linked List 链表