• 数据结构和算法系列2 线性表之链表


    上一篇我们总结完了顺序表,这一篇我们要总结的是线性表的链表,我想从以下几点进行总结。

    1,为什么要使用链表?
    2,链表的存储结构?
    3,链表的常用操作代码实现?

    1,为什么要使用链表

    通过上一篇的学习,我们知道顺序表存在一些问题,主要有以下两个方面。

    1,顺序表的长度是固定的,如果超出分配的长度就会造成溢出,如果存放的数据太少则会造成空间浪费。
    2,在插入元素和删除元素时(尤其不在尾部时),会移动大量的元素,造成性能和效率低下。

    基于以上问题,使用链表可以很好地避免顺序表中出现的问题。这也是我们要使用链表的原因。

    2,链表的存储结构

    ds06

    从上图可以看出,单链表中的每个结点都包含一个“数据域”和一个“指针域”。“数据域”中包含当前结点的数据,“指针域”包含下一节点的存储地址,头指针head是指向开始结点的,结束结点没有后继结点,所以结束结点的指针域为空,即null。

    3,链表的常用操作及实现代码

    链表常用的操作有:

    1,插入结点到表头

    思路:将head头指针的next指针给新增结点的next,然后将整个新增结点给head头指针的next。因此时间复杂度为O(1)。

    示意图:

    ds007
    2,插入结点到表尾

    思路:插入方法与插入到表头一样,只不过多一个步骤就是通过head头指针循环找到终端结点。因此时间复杂度为O(n)。

    示意图:

    ds07
    3,插入结点(1≤i≤ListLength(L))

    思路:插入方法与插入到表头一样,多循环查找当前结点的动作。因此时间复杂度为O(n)。

    示意图:

    ds08
    4,删除结点

    思路:同插入结点一样,时间复杂度为O(n)。

    示意图:

    ds09
    5,查找结点

    思路:与插入结点和删除结点方法类似,时间复杂度为O(n)。
    6,获取链表长度

    思路:不像顺序表是连续存储的,获取表的长度非常容易。在链表中,数据不是连续存储的,因此需要循环遍历才能求得链表的长度,所以时间复杂度为O(n)。

    下面是具体的实现代码。

    C#版:

    namespace DS.Model
    {
        /// <summary>
        /// 学生实体
        /// </summary>
        public class Student
        {
            public int ID { get; set; }
            public string Name { get; set; }
            public int Age { get; set; }
        }
    }
    
    namespace DS.BLL
    {
        /// <summary>
        /// 封装链表的常用操作
        /// </summary>
        public class ChainListBLL
        {
            /// <summary>
            /// 插入结点在表头
            /// </summary>
            /// <typeparam name="T"></typeparam>
            /// <param name="head"></param>
            /// <param name="data"></param>
            /// <returns></returns>
            public static Node<T> InsertFirst<T>(Node<T> head, T data)
            {
                //创建一个新结点
                Node<T> node = new Node<T>();
                node.data = data;
                node.next = head;
    
                head = node;
    
                return head;
            }
    
            /// <summary>
            /// 插入结点在表尾
            /// </summary>
            /// <typeparam name="T"></typeparam>
            /// <param name="head"></param>
            /// <param name="data"></param>
            /// <returns></returns>
            public static Node<T> InsertEnd<T>(Node<T> head, T data)
            {
                //创建一个新结点
                Node<T> node = new Node<T>();
                node.data = data;
                node.next = null;
    
                //空链表直接返回新增的结点
                if (head == null)
                {
                    head = node;
                    return head;
                }
    
                GetLastNode(head).next = node;
                return head;
            }
    
            /// <summary>
            /// 插入结点(在包含关键字key的结点之后插入新的结点)
            /// </summary>
            /// <typeparam name="T"></typeparam>
            /// <param name="head"></param>
            /// <param name="data"></param>
            /// <returns></returns>
            public static Node<T> Insert<T,W>(Node<T> head,string key,Func<T,W> where, T data) where W:IComparable
            { 
                //检查链表是否为空
                if (head == null) return null;
    
                //查找包含关键字key的结点
                if (where(head.data).CompareTo(key) == 0)
                {
                    Node<T> node = new Node<T>();
                    node.data = data;
                    node.next = head.next; //注意这里顺序不要弄反了
                    head.next = node;
                }
    
                Insert(head.next,key,where,data); //用递归继续查找下一个结点
                return head;
            }
    
            /// <summary>
            /// 删除结点(删除包含关键字key的结点)
            /// </summary>
            /// <typeparam name="T"></typeparam>
            /// <typeparam name="W"></typeparam>
            /// <param name="head"></param>
            /// <param name="key"></param>
            /// <param name="where"></param>
            /// <returns></returns>
            public static Node<T> Delete<T, W>(Node<T> head, string key, Func<T, W> where) where W : IComparable
            {
                if (head == null) return null;
    
                //如果只有一个结点
                if (where(head.data).CompareTo(key) == 0)
                {
                    if (head.next != null) head = head.next; //向后移动指针
                    else return head = null;
                }
                else
                {
                    //判断此结点是否是要删除结点的前一结点
                    while (head.next != null && where(head.next.data).CompareTo(key) == 0)
                    {
                        head.next = head.next.next;
                    }
                }
    
                Delete(head.next,key,where); //使用递归继续查找
                return head;
            }
    
            /// <summary>
            /// 查找结点(查找包含关键字key的结点)
            /// </summary>
            /// <typeparam name="T"></typeparam>
            /// <typeparam name="W"></typeparam>
            /// <param name="head"></param>
            /// <param name="key"></param>
            /// <param name="where"></param>
            /// <returns></returns>
            public static Node<T> GetNodeByKey<T, W>(Node<T> head, string key, Func<T, W> where) where W : IComparable
            {
                if (head == null) return null;
    
                if (where(head.data).CompareTo(key) == 0) return head;
                return GetNodeByKey(head.next,key,where);
            }
    
            /// <summary>
            /// 获取链表长度
            /// </summary>
            /// <typeparam name="T"></typeparam>
            /// <param name="head"></param>
            /// <returns></returns>
            public static int GetLength<T>(Node<T> head)
            {
                int count = 0;
    
                while (head != null)
                {
                    head = head.next; //移动指针
                    count++;
                }
    
                return count;
            }
    
            private static Node<T> GetLastNode<T>(Node<T> head)
            {
                if (head.next == null) return head; //最后结点
                return GetLastNode(head.next); //使用递归继续查找
            }
            
        }
    
    
        /// <summary>
        /// 封装链表
        /// </summary>
        /// <typeparam name="T"></typeparam>
        public class Node<T>
        {
            public T data; //数据
            public Node<T> next; //指针
        }
    }
    
    namespace ChainList.CSharp
    {
        class Program
        {
            static void Main(string[] args)
            {
                //实例化一个链表对象
                Node<Student> node = null;
    
                //插入结点在表头
                Console.WriteLine("
    ****************插入三条数据在表头**********************
    ");
                node = ChainListBLL.InsertFirst(node, new Student { ID = 1, Name = "a", Age = 10 });
                node = ChainListBLL.InsertFirst(node, new Student { ID = 2, Name = "b", Age = 11 });
                node = ChainListBLL.InsertFirst(node, new Student { ID = 3, Name = "c", Age = 12 });
                Display(node);
    
                //插入结点在表尾
                Console.WriteLine("
    ****************插入一条数据在表尾**********************
    ");
                node = ChainListBLL.InsertEnd(node, new Student { ID = 4, Name = "d", Age = 13 });
                Display(node);
    
                //插入结点(在包含关键字key的结点之前插入新的结点)
                Console.WriteLine("
    *************插入结点(在包含关键字key的结点之后插入新的结点)***********
    ");
                Console.WriteLine("将ID=5的结点插入到包含'b'关键字的结点之后");
                node = ChainListBLL.Insert(node, "b", p => p.Name, new Student { ID = 5, Name = "e", Age = 14 });
                Display(node);
    
                //删除结点(删除包含关键字key的结点)
                Console.WriteLine("
    *************删除结点(删除包含关键字key的结点)***********
    ");
                Console.WriteLine("删除Name='c'的结点");
                node = ChainListBLL.Delete(node,"c",p=>p.Name);
                Display(node);
    
                //查找结点(查找包含关键字key的结点)
                Console.WriteLine("
    *************查找结点(查找包含关键字key的结点)***********
    ");
                Console.WriteLine("查找Name='d'的结点");
                var singNode = ChainListBLL.GetNodeByKey(node, "d", p => p.Name);
                DisplaySingle(singNode);
    
                //获取链表长度
                Console.WriteLine("
    *************获取链表长度***********
    ");
                Console.WriteLine("目前链表中有{0}个结点",ChainListBLL.GetLength(node));
    
    
                Console.ReadKey();
            }
    
            /// <summary>
            /// 展示链表数据
            /// </summary>
            /// <param name="head"></param>
            private static void Display(Node<Student> head)
            {
                Console.WriteLine("
    ****************开始展示链表数据**********************
    ");
    
                while(head!=null)
                {
                    Console.WriteLine("ID={0},Name={1},Age={2}",head.data.ID,head.data.Name,head.data.Age);
                    head=head.next; //向后移动指针
                }
    
                Console.WriteLine("
    ****************链表数据展示完成**********************
    ");
            }
    
            private static void DisplaySingle(Node<Student> head)
            {
                if (head != null) Console.WriteLine("ID={0},Name={1},Age={2}", head.data.ID, head.data.Name, head.data.Age);
                else Console.WriteLine("未查找到数据!");
            }
        }
    }

    程序运行结果:

    ds10

    ds11

     

    C语言版:

    #include "stdio.h"    
    #include "string.h"
    #include "ctype.h"      
    #include "stdlib.h"   
    #include "io.h"  
    #include "math.h"  
    #include "time.h"
    
    #define OK 1
    #define ERROR 0
    #define TRUE 1
    #define FALSE 0
    
    typedef int Status;/* Status是函数的类型,其值是函数结果状态代码,如OK等 */
    typedef int ElemType;/* ElemType类型根据实际情况而定,这里假设为int */
    typedef struct Node
    {
        ElemType data; //数据域
        struct Node *next; //指针域
    }Node;
    typedef struct Node *ChainList; /* 定义链表 */
    Node *p;//定义一个指向结点的指针变量
    ChainList head; //定义指向链表的头指针
    
    /*初始化*/
    Status Init(ChainList *head)
    {
        //创建头结点,并使头指针指向头结点
        *head=(ChainList)malloc(sizeof(Node));
    
        //分配存储地址失败
        if (*head==NULL) return ERROR;
    
        //指针域为空
        (*head)->next=NULL;
        return OK;
    }
    
    /*插入结点(在以head为头指针的带头结点的链表中第i个结点的位置上插入新的结点s)*/
    /************************************************************************/
    /* 思路:由于第i个结点的存储地址是存储在第i-1个结点的指针域next中,因此,先使
    p指向第i-1个结点,然后生成一个数据域值为e的新结点s,再进行插入操作*/
    /************************************************************************/
    Status Insert(ChainList head,int i,ElemType e)
    {
        int j=0;
        //声明一结点p指向链表头结点,s表示要插入的新结点
        ChainList p,s;
        p=head;
    
        //使p指向新i-1个结点
        while(p!=NULL&&j<i-1)
        {
            p=p->next; //移动指针
            j++;
        }
    
        //判断插入位置是否合法
        if (p==NULL||j>i-1) return ERROR;
    
        s=(ChainList)malloc(sizeof(Node));//创建新结点
        s->data=e;
        s->next=p->next;
        p->next=s;
        return OK;
    }
    
    /*删除结点(在以head为头指针带头结点的链表中删除第i个结点)*/
    /************************************************************************/
    /* 思路:由于第i个结点的存储地址是存储在第i-1个结点的指针域next中,因此要先使p
    指向第i-1个结点,然后使得p->next指向第i+1个结点,再将第i个结点释放掉*/
    /************************************************************************/
    Status Delete(ChainList head,int i)
    {
        int j=0;
        ElemType e;
        //声明一结点p指向链表头结点,s表示要插入的新结点
        ChainList p,s;
        p=head;
    
        //使p指向第i-1个结点
        while(p!=NULL&&j<i-1)
        {
            p=p->next;//移动指针
            j++;
        }
    
        //删除位置错误
        if (p==NULL) return ERROR;
        else
        {
            s=p->next;
            p->next=s->next;
            e=s->data;
            free(s); //free函数释放资源
            return OK;
        }
    }
    
    /*获取链表的长度*/
    /************************************************************************/
    /* 思路:遍历链表,用计数器计算循环次数*/
    /************************************************************************/
    int GetLength(ChainList head)
    {
        int i=0;
    
        //指向第一个结点
        ChainList p=head->next;
    
        while(p!=NULL)
        {
            p=p->next;
            i++;
        }
    
        return i;
    }
    
    /*获取第i个元素的值*/
    /************************************************************************/
    /* 思路:由于第i个结点的存储地址是存储在第i-1个结点的指针域next中,因此要先使p
    指向第i-1个结点,然后取第i-1个结点的值*/
    /************************************************************************/
    Status GetDataByIndex(ChainList head,int i,ElemType *e)
    {
        int j=0;
    
        ChainList p,s;
    
        //让p指向第一个结点
        p=head->next;
    
        while(p!=NULL&&j<i-1)
        {
            p=p->next;
            j++;
        }
    
        if (p==NULL||j>i-1) return ERROR;
        *e=p->data;
        return OK;
    }
    
    /*展示数据*/
    Status Display(ChainList head)
    {
        ChainList p=head->next;
        printf("
    ****开始展示数据****
    ");
        while(p!=NULL)
        {
            printf("%d ",p->data);
            p=p->next;
        }
        printf("
    ****展示完毕****
    ");
        return OK;
    }
    
    
    void main()
    {
        ChainList head;
        ElemType e;
        Status i;
        int j,k;
    
        //初始化链表
        printf("
    *****************初始化************************
    ");
        i=Init(&head);
        printf("初始化后,链表的长度为:%d
    ",GetLength(head));
    
        //插入结点(数据)
        printf("
    *****************插入10条数据************************
    ");
        for (j=0;j<10;j++)
        {
            i=Insert(head,1,j);
        }
        printf("插入成功");
        Display(head);
    
        //删除结点(数据)
        printf("
    *****************删除第2条数据************************
    ");
        i=Delete(head,2);
        printf("删除成功");
        Display(head);
    
        //查找结点
        printf("
    *****************获取第5个结点数据*********************
    ");
        i=GetDataByIndex(head,5,&e);
        printf("%d
    ",e);
    
        //获取链表长度
        printf("
    *****************获取链表当前长度**********************
    ");
        k=GetLength(head);
        printf("当前长度为%d
    ",k);
    
        getchar();
    }

    程序运行结果:

    ds12

  • 相关阅读:
    纹理加载和异步
    地板上创建批量小方块
    创建自定义几何体(以立方体为例)
    WTForms
    angular和vue的差别
    vuejs简单介绍特点
    angularjs简单介绍和特点
    flask重要点
    redis
    DRF之认证组件源码解析
  • 原文地址:https://www.cnblogs.com/mcgrady/p/3209419.html
Copyright © 2020-2023  润新知