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

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

相关文章

来自专栏tkokof 的技术,小趣及杂念

疑难杂症小记 - 浮点运算的精度问题

result_2 = (int)(num * test) = (int)(160 * 1.3) = 208, 为什么程序会输出 207, WTF ? 更加诡...

421
来自专栏书山有路勤为径

最长回文串-哈希表

已知一个只包括大小写字符的字符串,求用该字符串中的字符可以生 成的最长回文字符串长度。 例如 s = “abccccddaa”,可生成的最长回文字符串长度为9...

722
来自专栏Golang语言社区

深入解析快速排序算法的原理及其Go语言版实现

快速排序是一种基于分治技术的重要排序算法。不像归并排序是按照元素在数组中的位置对它们进行划分,快速排序按照元素的值对它们进行划分。具体来说,它对给定数组中的元素...

3225
来自专栏mukekeheart的iOS之旅

No.005 Longest Palindromic Substring

5. Longest Palindromic Substring Total Accepted: 120226 Total Submissions: 50952...

2395
来自专栏Python攻城狮

科学计算工具Numpy1.ndarray的创建与数据类型2.ndarray的矩阵运算ndarray的索引与切片3.ndarray的元素处理元素判断函数元素去重排序函数4.2016年美国总统大选民意调查

Numpy:提供了一个在Python中做科学计算的基础库,重在数值计算,主要用于多维数组(矩阵)处理的库。用来存储和处理大型矩阵,比Python自身的嵌套列表结...

993
来自专栏编码前线

布隆过滤器(Bloom Filter)详解

直观的说,bloom算法类似一个hash set,用来判断某个元素(key)是否在某个集合中。 和一般的hash set不同的是,这个算法无需存储key的值,对...

804
来自专栏mathor

线性表(二)

1005
来自专栏文武兼修ing——机器学习与IC设计

AVL二叉树AVL二叉查找树

AVL二叉查找树 AVL二叉查找树是一种特殊的二叉查找树,其规定 每个节点的左子树和右子树的高度差最多是1 AVL调整算法 AVL树插入一个新的节点到某个...

2254
来自专栏曾大稳的博客

树(Tree)以及二叉树的遍历

树(tree) 是一种抽象数据类型(ADT)或是实作这种抽象数据类型的数据结构,用来模拟具有树状结构性质的数据集合。它是由n(n>0)个有限节点组成一个具有层次...

921
来自专栏我的技术专栏

数据结构图文解析之:树的简介及二叉排序树C++模板实现.

1004

扫码关注云+社区