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

为了表示每个数据元素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 条评论
登录 后参与评论

相关文章

来自专栏Vamei实验室

纸上谈兵: 树, 二叉树, 二叉搜索树

树的特征和定义 树(Tree)是元素的集合。我们先以比较直观的方式介绍树。下面的数据结构是一个树: ? 树有多个节点(node),用以储存元素。某些节点之间存在...

2057
来自专栏郭耀华‘s Blog

LeetCode第二天&第三天

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

28912
来自专栏猿人谷

单链表反转的分析及实现

我先画一个单链表,这个单链表有4个元素。我的思路就是,每次把第二个元素提到最前面来。比如下面是第一次交换,我们先让头结点的next域指向结点a2,再让结点a1...

83410
来自专栏java学习

每日一练(2017/5/17)

Java基础 | 数据库 | Android | 学习视频 | 学习资料下载 课前导读 ●回复"每日一练"获取以前的题目! ●答案公布时间:为每期发布题目的第二...

2806
来自专栏海天一树

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

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

6346
来自专栏Android知识点总结

看得见的数据结构Android版之二分搜索树篇

854
来自专栏老马说编程

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

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

19410
来自专栏java一日一条

Calendar 详解

究竟什么是一个 Calendar 呢?中文的翻译就是日历,那我们立刻可以想到我们生活中有阳(公)历、阴(农)历之分。它们的区别在哪呢?

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

数据结构之哈夫曼编码

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

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

Q112 Path Sum

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

3387

扫码关注云+社区