數據結構和算法系列2 線性表之鏈表

上一篇咱們總結完了順序表,這一篇咱們要總結的是線性表的鏈表,我想從如下幾點進行總結。node

1,爲何要使用鏈表?
2,鏈表的存儲結構?
3,鏈表的經常使用操做代碼實現?函數

1,爲何要使用鏈表

經過上一篇的學習,咱們知道順序表存在一些問題,主要有如下兩個方面。性能

1,順序表的長度是固定的,若是超出分配的長度就會形成溢出,若是存放的數據太少則會形成空間浪費。
2,在插入元素和刪除元素時(尤爲不在尾部時),會移動大量的元素,形成性能和效率低下。學習

基於以上問題,使用鏈表能夠很好地避免順序表中出現的問題。這也是咱們要使用鏈表的緣由。spa

2,鏈表的存儲結構

ds06

從上圖能夠看出,單鏈表中的每一個結點都包含一個「數據域」和一個「指針域」。「數據域」中包含當前結點的數據,「指針域」包含下一節點的存儲地址,頭指針head是指向開始結點的,結束結點沒有後繼結點,因此結束結點的指針域爲空,即null。3d

3,鏈表的經常使用操做及實現代碼

鏈表經常使用的操做有:指針

1,插入結點到表頭code

思路:將head頭指針的next指針給新增結點的next,而後將整個新增結點給head頭指針的next。所以時間複雜度爲O(1)。對象

示意圖:blog

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

相關文章
相關標籤/搜索