数据结构:线性表之链式存储结构

为了表示每个数据元素ai与其直接后继元素ai+1之间的逻辑关系,对数据ai,除了存储其自身的信息之外,还需存储一个指示其

直接后继的信息(即直接后继的存储位置)。这两部分信息组成数据元素ai的存储映像,称为结点(Node)。N个结点链结成一个链表,

即为线性表(a1,a2,...,an)的链式存储结构,因为此链表的每个节点中只包含一个指针域,所以叫做单链表。

我们把链表中的第一个结点的存储位置叫做头指针,,为了更方便地对链表进行操作,如删除第一个结点的特殊情况

(第一个结点没有前驱,而要摘除一个结点需要首先找到它的前驱才能做摘除操作),经常在单链表的第一个结点前附设一个结点,

称为头节点,这样头指针就指向了头节点。

单链表与数组的优缺点基本上是相反的,即单链表插入删除效率高,而查找需要遍历,效率较低。

示例程序:(改编自《大话数据结构》,增加了链表反转等)

#include<iostream>
#include<stdlib.h>
#include<time.h>
using namespace std;

typedef int ElemType;

typedef struct  Node
{
    ElemType data;
    struct Node *next;
} Node;

typedef Node *NodePtr;

bool InitList(NodePtr *Npp)
{
    *Npp = (NodePtr)malloc(sizeof(Node));/* 产生头结点,并使*Npp指向此头结点 */
    if (!(*Npp))
        return false; //分配失败

    (*Npp)->next = NULL;/* 指针域为空 */

    return true;
}

bool ListEmpty(NodePtr Np)
{
    if (Np->next == NULL)
        return true;
    else
        return false;
}

bool ClearList(NodePtr *Npp)
{
    cout << "Clear List..." << endl;
    NodePtr p = *Npp;
    if (!(p->next))
        return true;
    while (p->next)/*  没到表尾 */
    {
        NodePtr q = p->next;
        p->next = q->next;
        free(q);
    }

    return true;
}

int ListLength(NodePtr Np)
{
    cout << "List's length : ";
    NodePtr p = Np->next;/* p指向第一个结点 */
    int i = 0;

    while (p)
    {
        p = p->next;
        ++i;
    }

    return i;
}
/* 操作结果:用ptr返回Np中第pos个数据元素的值 */
bool GetElem(NodePtr Np, int pos, ElemType *ptr)
{
    cout << "Get Item from pos " << pos << " : ";
    NodePtr p = Np->next;
    int i = 1;
    /* p不为空或者计数器i还没有等于pos时,循环继续 */
    while (p && i < pos)
    {
        p = p->next;
        ++i;
    }

    if (!p)
        return false;
    *ptr = p->data;/*  取第pos个元素的数据 */
    return true;
}
/* 返回Np中第1个与Elem满足关系的数据元素的位序。 */
/* 若这样的数据元素不存在,则返回值为0 */
int LocateElem(NodePtr Np, ElemType Elem)
{
    cout << "Item " << Elem << "'s pos : ";
    NodePtr p = Np->next;
    int i = 1;

    while (p && p->data != Elem)
    {
        p = p->next;
        ++i;
    }

    if (!p)
        return 0;
    return i;
}
/* 操作结果:在Np中第pos个位置之前插入新的数据元素Elem,Np的长度加1 */
bool ListInsert(NodePtr *Npp, int pos, ElemType Elem)
{
    cout << "Insert List pos " << pos << " Item " << Elem << endl;
    NodePtr p = *Npp;
    int i = 1;

    while (p && i < pos)
    {
        p = p->next;
        ++i;
    }

    if (!p)
        return false;

    NodePtr In = (NodePtr)malloc(sizeof(Node));
    In->data = Elem;
    In->next = p->next;/* 将p的后继结点赋值给In的后继  */
    p->next = In;  /* 将In赋值给p的后继 */

    return true;
}
/* 删除Npp的第pos个数据元素,并用ptr返回其值,Npp的长度减1 */
bool ListDelete(NodePtr *Npp, int pos, ElemType *ptr)
{
    cout << "Delete List Item in pos " << pos << endl;
    NodePtr p = *Npp;
    int i = 1;

    while (p && i < pos)
    {
        p = p->next;
        ++i;
    }

    if (!p)
        return false;

    NodePtr q = p->next;
    *ptr = q->data;
    p->next = q->next;/* 将q的后继赋值给p的后继 */
    free(q);

    return true;
}

bool ListTraverse(NodePtr Np)
{
    cout << "List's Items : ";
    NodePtr p = Np->next;
    while (p)
    {
        cout << p->data << ' ';
        p = p->next;
    }
    cout << endl;
    return true;
}
/*  随机产生n个元素的值,建立带表头结点的单链线性表Npp(头插法) */
void CreateListHead(NodePtr *Npp, int num)
{
    cout << "Create List from Head ..." << endl;
    if (*Npp != NULL)
        free(*Npp);
    *Npp = (NodePtr)malloc(sizeof(Node));
    (*Npp)->next = NULL;/*  先建立一个带头结点的单链表 */
    srand(time(NULL));

    for (int i = 0; i < num; i++)
    {
        NodePtr p = (NodePtr)malloc(sizeof(Node));
        p->data = rand() % 100 + 1; //随机数
        p->next = (*Npp)->next;
        (*Npp)->next = p;/*  插入到表头 */
    }

}
/*  随机产生n个元素的值,建立带表头结点的单链线性表Npp(尾插法) */
void CreateListTail(NodePtr *Npp, int num)
{
    cout << "Create List from Tail ..." << endl;
    if (*Npp != NULL)
        free(*Npp);
    *Npp = (NodePtr)malloc(sizeof(Node));
    (*Npp)->next = NULL;
    srand(time(NULL));

    NodePtr tail = *Npp; /* tail为指向尾部的结点 */
    for (int i = 0; i < num; i++)
    {
        NodePtr p = (NodePtr)malloc(sizeof(Node));
        p->data = rand() % 100 + 1;
        tail->next = p;  /* 将表尾终端结点的指针指向新结点 */
        tail = p; /* 将当前的新结点定义为表尾终端结点 */
    }

    tail->next = NULL;
}
/* 反转单链表*/
NodePtr ReverseList(NodePtr head)
{
    cout << "Reverse List ...." << endl;
    if(NULL == head->next || NULL == head->next->next)
        return head;
    NodePtr p;
    NodePtr q;
    NodePtr r;
    p = head->next;
    q = p->next;
    head->next->next = NULL;
    while(q)
    {
        r = q->next; //
        q->next = p;
        p = q; //
        q = r; //
    }
    head->next = p;
    return head;
}

int main(void)
{
    NodePtr Np; //头指针
    InitList(&Np);
    for (int i = 1; i < 5; i++)
        ListInsert(&Np, i, i);

    if (!ListEmpty(Np))
        cout << ListLength(Np) << endl;
    ListTraverse(Np);

    cout << LocateElem(Np, 3) << endl;

    int get;
    GetElem(Np, 2, &get);
    cout << get << endl;

    ClearList(&Np);
    cout << ListLength(Np) << endl;

    CreateListHead(&Np, 10);
    ListTraverse(Np);
    int result;
    ListDelete(&Np, 5, &result);
    ListTraverse(Np);

    ClearList(&Np);
    CreateListTail(&Np, 10);
    ListTraverse(Np);
    ListTraverse(ReverseList(Np));

    ClearList(&Np);

    return 0;
}

输出为:

注意:程序中使用的单链表反转的方法是:使用p和q连个指针配合工作,使得两个节点间的指向反向,同时用r记录剩下的链表。

下图是不含头节点时的单链表反转情况,含头节点的单链表反转有细微区别,需小心。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏电光石火

java获取当前时间和前一天日期

String basePath = request.getScheme()+"://"+request.getServerName()+":"+request....

2828
来自专栏Java3y

二叉树就这么简单

一、二叉树就是这么简单 本文撇开一些非常苦涩、难以理解的概念来讲讲二叉树,仅入门观看(或复习)…. 首先,我们来讲讲什么是树: 树是一种非线性的数据结构,相对于...

5378
来自专栏电光石火

java获取当前时间和前一天日期

String basePath = request.getScheme()+"://"+request.getServerName()+":"+request....

6015
来自专栏郭耀华‘s Blog

LeetCode第二天&第三天

leetcode 第二天 2017年12月27日 4.(118)Pascal's Triangle ? JAVA class Solution { pu...

30412
来自专栏海天一树

用递归法把二叉树的叶子结点按从左到右的顺序连成一个单链表

一、例子 ? 1.png 上图中的二叉树的叶子结点,按从左到右的顺序连成的单链表如下图所示: ? 2.png 二、定义数据结构 typedef struct t...

7556
来自专栏一枝花算不算浪漫

Java中常见数据结构List之LinkedList

3255
来自专栏数据结构与算法

24:打印月历

24:打印月历 查看 提交 统计 提问 总时间限制: 1000ms 内存限制: 65536kB描述 给定年月,打印当月的月历表。 输入输入为一行两个整数,...

3826
来自专栏老马说编程

(32) 剖析日期和时间 / 计算机程序的思维逻辑

本节和下节,我们讨论在Java中如何进行日期和时间相关的操作。 日期和时间是一个比较复杂的概念,Java API中对它的支持不是特别好,有一个第三方的类库反而特...

21310
来自专栏Bingo的深度学习杂货店

Q112 Path Sum

Given a binary tree and a sum, determine if the tree has a root-to-leaf path suc...

3487
来自专栏测试开发架构之路

数据结构之哈夫曼编码

例题: 假设一个文本文件TFile中只包含7个字符{A,B,C,D,E,F,G},这7个字符在文本中出现的次数为{5,24,7,17,34,5,13} 利用哈夫...

4848

扫码关注云+社区

领取腾讯云代金券