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

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("\n****************插入三条数据在表头**********************\n");
            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("\n****************插入一条数据在表尾**********************\n");
            node = ChainListBLL.InsertEnd(node, new Student { ID = 4, Name = "d", Age = 13 });
            Display(node);

            //插入结点(在包含关键字key的结点之前插入新的结点)
            Console.WriteLine("\n*************插入结点(在包含关键字key的结点之后插入新的结点)***********\n");
            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("\n*************删除结点(删除包含关键字key的结点)***********\n");
            Console.WriteLine("删除Name='c'的结点");
            node = ChainListBLL.Delete(node,"c",p=>p.Name);
            Display(node);

            //查找结点(查找包含关键字key的结点)
            Console.WriteLine("\n*************查找结点(查找包含关键字key的结点)***********\n");
            Console.WriteLine("查找Name='d'的结点");
            var singNode = ChainListBLL.GetNodeByKey(node, "d", p => p.Name);
            DisplaySingle(singNode);

            //获取链表长度
            Console.WriteLine("\n*************获取链表长度***********\n");
            Console.WriteLine("目前链表中有{0}个结点",ChainListBLL.GetLength(node));


            Console.ReadKey();
        }

        /// <summary>
        /// 展示链表数据
        /// </summary>
        /// <param name="head"></param>
        private static void Display(Node<Student> head)
        {
            Console.WriteLine("\n****************开始展示链表数据**********************\n");

            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("\n****************链表数据展示完成**********************\n");
        }

        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("\n****开始展示数据****\n");
    while(p!=NULL)
    {
        printf("%d ",p->data);
        p=p->next;
    }
    printf("\n****展示完毕****\n");
    return OK;
}


void main()
{
    ChainList head;
    ElemType e;
    Status i;
    int j,k;

    //初始化链表
    printf("\n*****************初始化************************\n");
    i=Init(&head);
    printf("初始化后,链表的长度为:%d\n",GetLength(head));

    //插入结点(数据)
    printf("\n*****************插入10条数据************************\n");
    for (j=0;j<10;j++)
    {
        i=Insert(head,1,j);
    }
    printf("插入成功");
    Display(head);

    //删除结点(数据)
    printf("\n*****************删除第2条数据************************\n");
    i=Delete(head,2);
    printf("删除成功");
    Display(head);

    //查找结点
    printf("\n*****************获取第5个结点数据*********************\n");
    i=GetDataByIndex(head,5,&e);
    printf("%d\n",e);

    //获取链表长度
    printf("\n*****************获取链表当前长度**********************\n");
    k=GetLength(head);
    printf("当前长度为%d\n",k);

    getchar();
}

程序运行结果:

ds12

posted on 2013-07-23 21:19  永远的麦子  阅读(2546)  评论(1编辑  收藏  举报