数据结构--单向链表

C语言中,我们在使用数组时,会需要对数组进行插入和删除的操作,这时就需要移动大量的数组元素,但在C语言中,数组属于静态内存分配,数组在定义时就必须指定数组的长度或者初始化。这样程序一旦运行,数组的长度就不能再改变,若想改变,就只能修改源代码。实际使用中数组元素的个数也不能超过数组元素的最大长度,否则就会发生下标越界的错误(这是新手在初学C语言时肯定会遇到的问题,相信老师也会反复强调!!!但这种问题肯定会遇到,找半天找不到错误在哪,怪我咯???)。另外如果数组元素的使用低于最大长度,又会造成系统资源的浪费,会导致降低空间使用效率。

那有没有更合理的使用系统资源的方法呢?比如,但需要添加一个元素时,程序就可以自动的申请内存空间并添加新的元素,而当需要减少一个元素时,程序又可以自动地释放该元素占用的内存空间。我们聪明的祖先早就意识到了这个问题,于是就有了动态数据结构--链表结构(Linked list)。它主要是利用动态内存分配、使用结构体并配合指针来实现的一种数据结构。

链表有三种不同的类型:单向链表,双向链表以及循环链表。今天我们只对单向链表做详细的说明。

链表中最简单的一种是单向链表,它包含两个域,一个信息域和一个指针域。这个链接指向列表中的下一个节点,而最后一个节点则指向一个空值(NULL)。

单向链表的存储结构

/*单向链表的代码表示*/
struct node
{
    int data;  // 数据域
    struct node *next;  // 指向下一个节点的指针
};

接下来进入正题,分别详细讲一下单向链表的插入、删除节点以及插入节点操作。

  1. 单向链表的建立

建立一个单向链表,我们可以使用向链表中添加节点的方式。首先,要为新建的节点动态申请内存空间,让指针变量指向这个新建节点,然后将新建节点添加到链表中,这时,我们需要考虑以下两种情况:

(1)若原链表为空,则将新建节点设置为头节点

(2)若原链表为非空,则将新建节点添加到表尾

具体代码如下:

#include "stdio.h"
#include "stdlib.h"

struct link \*AppendNode(struct link \*head);
void DisplayNode(struct link *head);
void DeleteMemory(struct link *head);

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

int main(int argc, char const *argv\[\])
{
    int i = 0;
    char c;
    struct link *head = NULL;   //链表头指针
    printf("Append a new node(y/n)?");
    scanf("%c", &c);

    while(c == ‘Y‘ || c == ‘y‘){
        head = AppendNode(head);    //向head为头指针的链表末尾添加节点
        DisplayNode(head);
        printf("Append a new node(y/n)?");
        scanf(" %c", &c);
        i++;
    }

    printf("%d new nodes have been appened!\\n");
    DeleteMemory(head);
    return 0;
}

// 新建一个节点并添加到链表末尾,返回添加节点后的链表的头指针
struct link \*AppendNode(struct link \*head){
    struct link \*p = NULL, \*pr = head;
    int data;

    p = (struct link *)malloc(sizeof(struct link)); // 通过malloc函数动态的申请内存,注意结构体占用内存的大小只能用sizeof()获取
    if (p == NULL){
        printf("No enough memory to allocate!\\n");
        exit(0);
    }
    if (head == NULL){  //原链表为空
        head = p;
    }else{              // 原链表为非空,则将新建节点添加到表尾
        while(pr->next != NULL){    // 如果pr指向的不是表尾,则移动pr直到指向表尾
            pr = pr->next;
        }
        pr->next = p;
    }
    printf("Input node data:");
    scanf("%d",&data);      // 输入新建节点的数据
    p->data = data;
    p->next = NULL;         // 将新建节点置为表尾
    return head;
}

// 显示链表中所有的节点
void DisplayNode(struct link *head){
    struct link *p = head;
    int j = 1;
    while(p != NULL){       // p不在表尾,循环打印节点的值
        printf("%5d%10d\\n", j, p->data);
        p = p->next;
        j++;
    }
}

//释放head指向的链表中所有节点占用的内存
void DeleteMemory(struct link *head){
    struct link \*p = head, \*pr = NULL;
    while(p != NULL){   // p不在表尾,释放节点占用的内存
        pr = p;         // 在pr中保存当前节点的指针
        p = p->next;    // p指向下一个节点
        free(pr);       // 释放pr指向的当前节点占用的内存
    }
}

代码运行结果如下:

2. 单向链表的删除操作

链表的删除操作就是将待删除的节点从链表中断开,那么待删除节点的上一个节点就成为尾节点。在删除节点时,我们要考虑一下4种情况:

(1)若原链表为空,则不执行任何操作,直接退出程序

(2)若待删除节点是头节点,则将head指向当前节点的下一个节点,再删除当前节点

(3)若待删除节点不是头节点,则将前一节点的指针域指向当前节点的下一节点,即可删除当前节点。当待删除节点是尾节点时,由于p->next=NULL,因此执行pr->next = p->next后,pr->next的值也变为了NULL,从而使pr所指向的节点由倒数第二个节点变成尾节点。

(4)若待删除的节点不存在,则退出程序

注意:节点被删除后,只表示将它从链表中断开而已,它仍占用着内存,必须要释放这个内存,否则会出现内存泄漏。

删除一个节点的代码如下:

// 从head指向的链表中删除一个节点,返回删除节点后的链表的头指针
struct link \*DeleteNode(struct link \*head, int nodeData)
{
    struct link \*p = head, \*pr = head;
    if (head == NULL)       // 若原链表为空,则退出程序
    {
        printf("Linked Table is empty!\\n");
        return head;
    }
    while(nodeData != p->data && p->next != NULL)   // 未找到待删除节点,且没有到表尾
    {
        pr = p;         // 在pr中保存当前节点的指针
        p = p->next;    // p指向当前节点的下一节点
    }
    if (nodeData == p->data)    // 若当前节点就是待删除节点
    {
        if (p == head)  // 若待删除节点为头节点
        {
            head = p->next;     // 将头指针指向待删除节点的下一节点
        }
        else        // 若待删除节点不是头节点
        {
            pr->next = p->next; // 让前一节点的指针指向待删除节点的下一节点
        }
        free(p);    // 释放为已删除节点分配的内存
    }
    else    // 没有找到节点值为nodeData的节点
    {
        printf("This Node has not been found!\\n");
    }
    return head;        // 返回删除节点后的链表头指针
}

3. 单链表的插入操作

向一个链表中插入一个新节点时,首先要新建一个节点,并将新建节点的指针域初始化为空NULL,然后在链表中寻找适当的位置执行节点插入操作,此时需要考虑下面4种情况:

(1)若原链表为空,则将新建节点p作为头节点,让head指向新节点p

(2)若原链表为非空,折按新建节点的值的大小(假设原链表已按节点值升序排列)确定插入新节点的位置。若在头结点前插入新节点,则将新节点的指针域指向原链表的头结点,并且让head指向新节点p

(3)若在原链表中间插入新节点,则将新节点p的指针域指向下一节点,并且让前一节点的指针域指向新建节点p

(4)若在表尾插入新节点,则将尾节点的指针域指向新节点p

具体代码如下:

// 在已按升序排列的链表中插入一个新节点,返回插入节点后的链表头指针
struct link \*InsertNode(struct link \*head, int nodeData)
{
    struct link \*pr = head, \*p = head, *temp = NULL;
    p = (struct link *)malloc(sizeof(struct link)); // 给新建节点动态申请内存空间
    if (p == NULL)      // 若动态申请内存失败,则退出程序
    {
        printf("No enough memory!\\n");
        exit(0);
    }

    p->next = NULL; // 将新建节点的指针域初始化为空
    p->data = nodeData; // 将新建节点的数据域初始化为nodeData

    if (head == NULL)   // 若原链表为空
    {
        head = p;   // 将新建节点作为头节点
    }
    else    // 若原链表为非空
    {
        // 未找到新建节点的插入位置并且没有到尾节点
        while(pr->data < nodeData && pr->next != NULL)
        {
            temp = pr;  // 在temp中保存当前节点pr的指针
            pr = pr->next;  // pr跳到下一节点
        }
        // 找到需要插入的位置
        if (pr->data >= nodeData)
        {
            if (pr == head) // 若当前节点为头节点,则将新建节点插入头节点之前
            {
                p->next = head; // 将新节点的指针域指向原链表的头节点
                head = p;   // head指向新建节点
            }
            else    // 在原链表中插入新节点
            {
                pr = temp;
                p->next = pr->next; // 新建节点的指针域指向当前节点的下一节点
                pr-next = p;        // 当前节点的下一节点指向新节点
            }
        }
        else    // 新建节点的值为最大值,插在原链表尾部
        {
            pr->next = p;   // 原链表的尾节点指向新节点
        }
    }
    return head;    // 返回插入新节点后的链表的头指针
}

到此,对于单链表的操作已经介绍完了。通过写这篇博客,我也深刻学习了单链表的结构和一些主要操作,在写作的过程中也翻阅了很多资料,让我意识到数据结构的重要性,不懂数据结构,你永远只能当一个码农。

原文地址:https://www.cnblogs.com/bilberry/p/10193302.html

时间: 2024-10-10 18:27:41

数据结构--单向链表的相关文章

数据结构:单向链表系列6--交换相邻两个节点1(交换数据域)

给定一个单向链表,编写函数交换相邻 两个元素 输入: 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 输出: 2 -> 1 -> 4 -> 3 -> 6 -> 5 -> 7 输入: 1 -> 2 -> 3 -> 4 -> 5 -> 6 输出: 2 -> 1 -> 4 -> 3 -> 6 -> 5 通过观察发现:当输入的与元素个数是单数的时候,最后一位不参与交换

数据结构-单向链表 C和C++的实现

数据结构,一堆数据的存放方式. 今天我们学习数据结构中的 链表: 数组,大家相当熟悉的存放数据方式,而链表是数组的一种特殊方式,它每个节点包括两个部分: 数据域:存放数据,此部分与数组相同 指针域:存放了下一个节点的地址 链表比数组多了指针域,因为链表需要通过上一个节点的指针域去找下一个数据,比如有一个链表ABCD四个节点,我们要访问D里边的数据.操作如下: 先通过A节点的指针域找到B节点 再通过B节点的指针域找到C节点 再通过C节点的指针域找到D节点 获取D节点数据域的数据 对比数组直接通过下

数据结构-单向链表相关算法

#include <stdio.h>#include <stdlib.h>#define OVERFLOW -2#define OK 1#define ERROR 0typedef int ElemType;//单向链表结构体typedef struct LNode {    ElemType data;    struct LNode *next;}LNode,*LinkList; LinkList CreateList_L(LinkList L,int n);void Trav

数据结构:单向链表(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 Recursiv

数据结构:单向链表系列5--在链表中查找元素

在链表中查找元素 函数签名: 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 prese

数据结构:单向链表系列8--反转链表

业务需求:给定一个指向头指针的链表,反转链表.实现过程:更改相邻节点之间的链域. 例: 输入: 1->2->3->4->NULL输出:4->3->2->1->NULL 输入:1->2->3->4->5->NULL输出:5->4->3->2->1->NULL 输入: NULL输出: NULL 输入: 1->NULL输出: 1->NULL 迭代法: 空间复杂度:O(1),时间复杂度:O(n)

Python3中定义一个单向链表

链表是由节点构成的,一个指针代表一个方向,如果一个构成链表的节点都只包含一个指针,那么这个链表就是单向链表. 单向链表中的节点不光有代表方向的指针变量,也有值变量.所以我们定义链表,就是要定义链表中的节点,对链表的操作最后也就是对节点的操作. 这些包含数据的节点们在一种指定的结构下连接起来,成为了一种数据结构——单向链表.以上是我对单向链表的理解. 以下是我用python3对单向链表这种数据结构的一种实现: ''' Python3版单向链表-单向链表简称单链表 单链表中所包含的基本操作: 初始化

数据结构之链表单向操作总结

链表是数据结构的基础内容之一,下面就链表操作中的创建链表.打印链表.求取链表长度.判断链表是否为空.查找结点.插入结点.删除结点.逆转链表.连接链表.链表结点排序等进行总结. 1.创建表示结点的类,因为链表操作中需要比较结点,因此结点需要实现comparable接口. public class Node implements Comparable<Node> { private Object data; private Node next; //构造函数 public Node() { thi

数据结构与算法-单向链表

概述 由于最近在工作中需要用到树形结构来解决一些问题,所以萌生了系统学习“数据结构和算法”的想法,于是乎从最简单的表结构开始.由于数组是最简单的表结构的实现,也是各个编程语言内置的数据类型,所以不做更多的记录.表结构中以下实现打算学习: LinkedList Stack Queue HashTable Dictionary 本篇为学习数据结构的第一篇随笔,从最简单的单向链表开始吧. 实现(C#) 平台:dotNet Core 1.0, C# IDE:VSCode 如果考虑算法复用的话,可以实现泛