Bootstrap

数据结构——链表和单向链表

1、链表的介绍

(1)定义

        链表是一种链式存储的线性表

        链表是一种基本的数据结构,它由一系列节点组成,每个节点包含一个值指向下一个节点的指针

节点如下图所示: 

        与数组不同,链表中的节点不一定是连续的存储空间,因此可以有效地利用内存空间

(2)特点:可以动态添加和删除节点,而不需要预先知道数据的数量,不擅长随机访问(要遍历)

(3)优缺点 

        优点:不要求大片连续空间,改变容量方便;可以动态的添加和删除节点

        缺点:不方便可随机存取,要耗费一定空间存放指针

2、链表的分类 

链表可以分为三种类型:

        单向链表:节点由2部分组成(数据域存储当前元素,指针域用来指向下一个节点),每个节点只有一个指针指向下一个节点,最后一个节点的指针指向NULL

        双向链表:节点由3部分组成(数据域存储当前元素,2个指针域:一个用来指向上一个节点地址,一个用来指向下一个节点地址),每个节点有两个指针,分别指向上一个节点和下一个节点,可以在O(1)时间内实现向前和向后遍历

        循环链表:在单向或双向链表的基础上,将最后一个节点的指针指向头节点,形成一个环

链表的种类其实有很多种,按照单双向、带头(没有元素值,只有指针)不带头、循环不循环,一共可以分为8种类型,但最常见就是单向链表和双向链表

3、链表的操作

        插入操作:可以在链表头或尾插入节点,也可以在指定位置插入节点(头插、尾插、中间插)

        删除操作:可以删除指定节点或按照值删除节点(头删、尾删、中间删)

        查找操作:可以查找指定节点或按照值查找节点

        遍历操作:可以遍历整个链表,输出每个节点的值或执行其他操作

4、 单向链表(单向、不带头、不循环)

4.1  01单向链表.h

#pragma once
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <Windows.h>
#include <stdlib.h>
#include <memory.h> 
#include <assert.h>
//定义单向、不带头、不循环链表的元素节点结构体类型
typedef int eleT;
typedef struct Link_Node {
    eleT val;  //数据域:存储节点元素值
    struct Link_Node* next;  //指针域,指向下一个节点
}LKNode;

void print_menu();
LKNode* init_List(eleT value);
void print_List(LKNode* LinkNode);
void head_Insert(LKNode** LinkNode, eleT value);
void tail_insert(LKNode* LinkNode, eleT value);
void head_delete(LKNode** LinkNode);
void tail_delete(LKNode** LinkNode);
LKNode* find_item(LKNode* LinkNode, eleT value);
void middle_delete(LKNode* LinkNode, eleT value);
void mid_insert(LKNode* temp, eleT newvalue);
void destroy(LKNode** LinkNode);

void fn3();

4.2 main.c

#include "01单向链表.h"
int main() {
    fn3();
    return 0;
}

4.3  01单向链表.c

#include "01单向链表.h"
//逻辑结构:线性结构
//存储结构(物理结构):链式存储(存储顺序和逻辑顺序不在乎是否一致)

//头节点变传地址,不变传值
void fn3() {
    int order = 0;
    print_menu();
    LKNode* LinkNode = NULL;  //存放头节点指针
    eleT value = 0;
    eleT newvalue = 0;
    LKNode* temp = NULL;
    while (1) {
        printf("请输入操作指令:");
        scanf("%d", &order);
        switch (order) {
        case 1:  //链表初始化,初始化一个头节点(包含元素)
            printf("请输入头节点元素值:");
            scanf(" %d", &value);
            LinkNode = init_List(value);
            break;
        case 2:  //打印链表,遍历
            print_List(LinkNode);
            break;
        case 3:  //链表头插
            printf("请输入要插入的元素:");
            scanf(" %d", &value);
            head_Insert(&LinkNode, value);
            break;
        case 4:  //链表尾插
            printf("请输入要尾插的元素:");
            scanf(" %d", &value);
            tail_insert(LinkNode, value);
            break;
        case 5:  //链表头删
            head_delete(&LinkNode);
            break;
        case 6:  //链表尾删
            tail_delete(&LinkNode);
            break;
        case 7:  //链表的查找
            //思路:找到了,返回当前元素节点的指针;找不到,返回NULL
            printf("请输入要查找的元素:");
            scanf(" %d", &value);
            temp = find_item(LinkNode, value);
            if (temp == NULL) {
                printf("没有此元素!\n");
            }else {
                printf("找到了,该元素值为:%d\n", temp->val);
            }
            break;
        case 8:  //链表的中间删,至少要3个元素
            printf("请输入要删除的元素:");
            scanf(" %d", &value);
            middle_delete(LinkNode, value);
            break;
        case 9:  //链表的中间插
            //要求:至少有1个元素
            printf("请输入要插入的元素:");
            scanf(" %d", &newvalue);
            printf("请输入要在哪个元素的后边插入:");
            scanf(" %d", &value);
            temp = find_item(LinkNode, value);
            if (temp == NULL) {
                printf("没有此元素,无法插入!\n");
            }else{
                mid_insert(temp, newvalue);
            }
            break;
        case 10:  //链表销毁
            destroy(&LinkNode);
            break;
        case 11:  //退出
            return;
        default:
            printf("输入错误,请重新输入!\n");
        }
    }
}
//打印菜单
void print_menu(){
    system("cls");  //屏幕清空
    printf("操作指令说明:\n");
    printf("1:链表初始化\n2:打印链表\n");
    printf("3:链表头插\n4:链表尾插\n");
    printf("5:链表头删\n6:链表尾删\n");
    printf("7:链表的查找\n8:链表的中间删\n");
    printf("9:链表的中间插\n10:链表销毁\n");
    printf("11:退出\n");
    printf("************************\n");
}

//链表初始化,初始化一个头节点(包含元素)
LKNode* init_List(eleT value) {
    //申请节点内存
    LKNode* newNode = (LKNode*)malloc(sizeof(LKNode));
    判断内存是否申请成功
    //if (!newNode) {
    //    printf("内存申请失败!\n");
    //    return;
    //}

    //补充两个调试代码常用的方法
    //exit(参数)  //用于终止程序执行,输出一些参数,一般用0表示正常退出,非0异常退出
    //assert(条件)  //条件为真,程序正常执行;条件为假,程序报错,退出(需要导入<assert.h>)
    
    //简化   判断内存是否申请成功
    assert(newNode);
    newNode->val = value;
    newNode->next = NULL;
    printf("初始化成功!\n");
    return newNode;
}

//打印链表,遍历
void print_List(LKNode* LinkNode) {
    assert(LinkNode);
    遍历
    //LKNode* temp = LinkNode;
    //while (temp->next != NULL) {
    //    printf("%d ", temp->val);
    //    temp = temp->next;
    //}
    //printf("%d ", temp->val);
    //另一种遍历方法
    LKNode* temp = LinkNode;
    while (1) {
        printf("%d ", temp->val);
        if (temp->next == NULL) {  //尾节点
            break;
        }
        temp = temp->next;  //temp是一个指针;temp->next也是一个指针,它存储在当前节点temp中,指向链表中的下一个节点,我将其理解为存储的是下一个节点的地址
    }
    printf("\n");
}

//链表头插
void head_Insert(LKNode** LinkNode, eleT value) {
    assert(*LinkNode);
    //(1)创建新节点
    LKNode* newNode = (LKNode*)malloc(sizeof(LKNode));
    assert(newNode);
    //(2)给新节点成员赋值
    newNode->val = value;
    newNode->next = *LinkNode;
    //(3)更新头节点
    *LinkNode = newNode;
    printf("头插成功!\n");
}

//链表尾插
void tail_insert(LKNode* LinkNode, eleT value) {
    assert(LinkNode);
    //(1)创建新节点
    LKNode* newnode = (LKNode*)malloc(sizeof(LKNode));
    assert(newnode);
    //(2)给新节点成员赋值
    newnode->val = value;
    newnode->next = NULL;
    //(3)将新节点放到链表的尾部
    //首先找到链表的尾节点
    LKNode* temp = LinkNode;
    while (1) {
        if (temp->next == NULL) {  //尾节点
            //是尾节点,将新节点插到尾节点的后边
            temp->next = newnode;
            break;
        }
        temp = temp->next;  //temp是一个指针,temp->next也是一个指针,它存储在当前节点temp中,指向链表中的下一个节点
    }
    printf("尾插成功!\n");
}

//链表头删
void head_delete(LKNode** LinkNode) {
    assert(*LinkNode);

    //(1)保存当前头节点的后一个节点
    LKNode* temp = (*LinkNode)->next;  

    //(2)释放原来头节点
    free(*LinkNode);  

    //(3)更新头节点
    *LinkNode = temp;  
    printf("头删成功!\n");
}

//链表尾删
void tail_delete(LKNode** LinkNode) {
    assert(*LinkNode);
    //需要考虑到只有一个节点的情况
    if ((*LinkNode)->next == NULL) {
        free(*LinkNode);  //释放当前节点
        *LinkNode = NULL;
    }else {
        //找到链表的倒数第二个元素节点
        LKNode* temp = *LinkNode;
        while (1) {
            if (temp->next->next == NULL) {  //temp->next:倒数第二个元素节点中的nexttemp->next->next:尾节点中next
                break;
            }
            temp = temp->next;
        }
        //释放尾节点
        free(temp->next);  //temp:倒数第二个元素节点
        temp->next = NULL;  //更新尾节点
        printf("尾节点删除成功!\n");
    }
}

//链表的查找
//思路:找到了,返回当前元素节点的指针;找不到,返回NULL
LKNode* find_item(LKNode* LinkNode, eleT value) {
    assert(LinkNode);
    LKNode* temp = LinkNode;
    while (temp) {
        if (temp->val == value) {
            //找到了
            return temp;
        }
        temp = temp->next;
    }
    return NULL;
}

//链表的中间删
void middle_delete(LKNode* LinkNode, eleT value) {
    assert(LinkNode);
    //查找到删除元素的前一个元素的地址
    LKNode* temp = LinkNode;
    while (temp) {
        if (temp->next->val == value) {  //temp:当前节点(删除元素的前一个元素)temp->next:当前节点的下一个节点
            //找到了,删除temp->next
            LKNode* ptr = temp->next;  //保存要删除的节点
            temp->next = temp->next->next;
            free(ptr);  //释放要删除的节点
            printf("删除成功!\n");
            break;
        }
        temp = temp->next;
        if (temp->next == NULL) {
            printf("没有此元素,无法删除!\n");
            break;
        }
    }
}

//链表的中间插
//要求:至少有1个元素
void mid_insert(LKNode* temp, eleT newvalue) {
    //(1)创建新节点
    LKNode* newnode = (LKNode*)malloc(sizeof(LKNode));
    assert(newnode);
    //(2)给新节点数据域赋值
    newnode->val = newvalue;
    //(3)将新节点插入
    newnode->next = temp->next;
    temp->next = newnode;
    printf("插入成功!\n");
}

//链表销毁
void destroy(LKNode** LinkNode) {
    //遍历出来一个一个删除,并更新头节点
    assert(*LinkNode);  //*LinkNode:头节点
    while (*LinkNode) {
        LKNode* temp = (*LinkNode)->next;
        free(*LinkNode);
        *LinkNode = temp;
    }
    *LinkNode = NULL;
    printf("销毁成功!");
}

;