数据结构-单向链表_数据结构

一、概念

对于顺序存储的结构,如数组,最大的缺点就是:插入删除 的时候需要移动大量的元素。所以,基于前人的智慧,他们发明了链表。

1.链表的定义

链就说明有一条链子,表就是一个结点,把结点用链子串起来,不就是链表了嘛

每个结点都有一个后继结点前驱结点,当然,第一个结点无前驱结点最后一个结点无后继结点

数据结构-单向链表_数据结构_02

A无前驱结点,A的后继结点是B;B的前驱结点是A,后继结点是C,C的前驱结点是B,C无后继结点

链表分为单向链表双向链表循环链表等等,我们本文介绍的是单向链表

2.结点结构体定义

typedef int DataType;

struct ListNode {

    DataType data;  // (1)数据域,可以是任意类型

    ListNode *next; // (2)指针域,指向后继结点

};

3.结点的创建

ListNode *ListCreateNode(DataType data) {

    ListNode *node = (ListNode *) malloc ( sizeof(ListNode) ); // (1)malloc函数在堆区开辟空间

    node->data = data;                                         // (2)数据域赋值

    node->next = NULL;                                         // (3)指针域置空(孤立结点)

    return node;                                               // (4)返回这个结点

}

二、链表的创建-尾插法

1.算法描述

 首先介绍 尾插法 ,顾名思义,即 从链表尾部插入 的意思,就是记录一个 链表尾结点,然后遍历给定数组,将数组元素一个一个插到链表的尾部,每插入一个结点,则将它更新为新的 链表尾结点。注意初始情况下,链表尾结点 为空。

数据结构-单向链表_数据结构_03

head代表链表头结点,vtx代表要加入的结点,tail代表尾结点

2.代码示例

ListNode *ListCreateListByTail(int n, int a[]) {

    ListNode *head, *tail, *vtx;         // (1)定义头结点,尾结点,插入的节点  
    int idx;                              

    if(n <= 0)

        return NULL;                     // (2)创建元素为0的时候,返回NULL  
    idx = 0;

    vtx = ListCreateNode(a[0]);          // (3)vtx指向创建好的结点  
    head = tail = vtx;                   // (4)初始情况下都指向vtx创建好的结点  

    while(++idx < n) {                   // (5)开始循环  
        vtx = ListCreateNode(a[idx]);    // (6) vtx指向新创建的结点
        tail->next = vtx;                // (7)  尾结点的指针域指向新创建的结点
        tail = vtx;                      // (8)  尾结点tail后移,指向新创建的结点,
        										//让tail始终指向最后一个节点

    }  
    return head;                         // (9) 返回头结点 
}

三、链表的创建-头插法

1.算法描述

头插法就是从头结点前面进行插入,所以插入的数据元素是逆序

特点是代码量短,时间复杂度低

2.代码示例

ListNode *ListCreateListByHead(int n, int *a) {

    ListNode *head = NULL, *vtx;       // (1)heda存储头结点地址,vtx存储当前要插入的元素  
    while(n--) {                       // (2) n个结点
        vtx = ListCreateNode(a[n]);    // (3)  vtx指向创建已好的结点
        vtx->next = head;              // (4)  将当前创建的结点的 后继结点 置为 链表的头结点head
        head = vtx;                    // (5)  将head置为vtx
    }  
    return head;                       // (6) 返回头结点 
}

数据结构-单向链表_头结点_04

四、链表的打印

1.打印的作用

可视化 能够帮助我们更好的理解数据结构。所以,对于一种数据结构,如何通过 输出函数 将它 打印到控制台 上,就成了我们接下来要做的事情。

2.代码示例

void ListPrint(ListNode *head) {

    ListNode *vtx = head;

    while(vtx) {                      // (1)从头结点开始遍历

        printf("%d -> ", vtx->data);  // (2)  输出数据域
        vtx = vtx->next;              // (3)  指向下一个结点	

    }

    printf("NULL\n");                 // (4)	代表结束

}

数据结构-单向链表_头结点_05

五、链表元素的查找

1.算法描述

 给定一个链表头head,并且给定一个值 数据结构-单向链表_数据结构_06,查找出这个链表上 数据域 等于 数据结构-单向链表_数据结构_06

查找的过程,就是对链表的遍历

2.代码示例

ListNode *ListFindNodeByValue(ListNode *head, DataType v) {

    ListNode *temp = head;       // (1) temp指向头结点 
    while(temp) {                // (2) 从头结点开始查找
        if(temp->data == v) {

            return temp;         // (3)  找到了,返回该结点对应的指针
        }  
        temp = temp->next;       // (4)  没找到,指向下一个结点
    }

    return NULL;                 // (5)  链表里没有该元素,返回NULL
}

时间复杂度,最坏的情况就是找不到,需要遍历整个链表,时间为O(n)

六、链表结点的插入

1.算法描述

我们需要在第i个结点后插入一个值为V的结点

首先我们需要先找到第i个位置

然后执行插入操作:

插入操作分为两步:第一步就是 创建结点 的过程;第二步,是断开之前第 数据结构-单向链表_头结点_08 个结点 和 第 数据结构-单向链表_头结点_09

个结点之间的链,并将新创建的结点放到这两个结点之间链起来

2.代码示例

ListNode *ListInsertNode(ListNode *head, int i, DataType v) {

    ListNode *pre, *vtx, *aft;                     // (1)定义三个指针  
    int j = 0;                                     // (2)计数器,j==i说明找到了我们需要的那个位置 
    pre = head;                                    // (3)pre指向头结点  
    while(pre && j < i) {                          // (4) pre不为空,j还没到i的位置继续遍历 
        pre = pre->next;                           // (5) pre指向下一个结点
        ++j;                                       // (6) j++ 
    }

    if(!pre) {  
        return NULL;                               // (7)如果没有找到,返回NULL  
    }

    vtx = ListCreateNode(v);                       // (8)找到了,创建结点  
    aft = pre->next;                              	//插入这段我用一张图来说明

    vtx->next = aft;                               

    pre->next = vtx;                               

    return vtx;                                      

}

数据结构-单向链表_链表_10

数据结构-单向链表_链表_11

插入的操作时间复杂度为O(1),但是寻找位置的时候,最坏情况下就是找不到该位置,时间为O(n)

七、链表结点的删除

1.算法描述

给定位置i,将i这个位置的结点删去,返回头结点

链表结点删除分为三种情况:

空链表

非空链表:删除链表头结点,删除链表非头结点

(1)空链表,直接返回NULL

(2)非空链表删除头结点,将头结点的下一个结点位置保存作为新头结点,释放头结点

(3)非空链表删除非头结点,遍历链表,找到要删除的结点,标记删除结点的前驱结点和后继结点,释放要删除的结点,将它的前驱结点和后继节点链起来

2.代码演示

ListNode *ListDeleteNode(ListNode *head, int i) {

    ListNode *pre, *del, *aft;

    int j = 0;

    if(head == NULL) {

        return NULL;              // (1)空链表,返回NULL  

    }

    if(i == 0) {                  // (2)删除第0个结点(头结点)  
        del = head;               // (3)标记头结点        
        head = head->next;        // (4)让头结点指向它的下一个结点  

        free(del);                // (5)释放第0个结点  
        return head;              // (6)返回新的头结点位置  

    }

    

    pre = head;                   // (7)从头结点开始遍历链表  

    while(pre && j < i - 1) {     // (8)找到要删除结点的前驱结点  

        pre = pre->next;

        ++ j;

    }

   

    del = pre->next;              // (9)标记删除结点del  
    aft = del->next;              // (11)标记删除结点的后继结点  
    pre->next = aft;              // (12)将删除结点的前驱结点和后继结点链上  
    free(del);                    // (13) 释放删除结点 
    return head;                  // (14) 返回头结点 
}

数据结构-单向链表_数据结构_12

数据结构-单向链表_头结点_13

八、链表的销毁

因为我们链表是在堆区里申请的,所以需要销毁

void ListDestroyList(ListNode **pHead) { // (1)这里必须用二级指针,
										 //	因为删除后需要将链表头置空
                                         // 普通的指针传参无法影响外部指针变量;  
    ListNode *head = *pHead;             
    while(head) {                         
        head = ListDeleteNode(head, 0);  
    }

    *pHead = NULL;                       
}