首页 > 其他分享 >线性表

线性表

时间:2023-02-10 21:31:52浏览次数:27  
标签:结点 线性表 head next 链表 指针

1. 线性表的定义和基本运算

  • 线性表的定义: 它是由n个数据元素A1,A2,... ,An组成的有限序列,注意两个词,有限和序列。通常记为 (A1, A2, ... , An)

  • 线性表的逻辑特征:

    1. 有且仅有一个称为开始的元素A1,它没有前趋,仅有一个直接后继A2;
    2. 有且仅有一个称为终端元素的An,它没有后继,仅有一个直接前趋;
    3. 其余元素A~i~(2 ≦ i ≦ n-1)称为内部元素,它们都有且仅有一个直接前趋A~i-1~和一个直接后继A~i+1~;
  • 线性表常见的运算:

    1. 置空表InitList(L),构造一个空的线性表L。
    2. 求表长ListLength(L),返回线性表L中元素个数,即表长。
    3. 取表中第i个元素GetNode(L,i), 若1 ≦ i ≦ ListLenght(L),则返回第i个元素A~i~。
    4. 按值查找LocateNode(L,x),在表L中查找第一个值为x的元素,并返回该元素在表L中的位置,若表中没有元素的值为x,则返回0值。
    5. 插入InsertList(L,i,x),在表的第i个元素之前插入一个值为x的新元素,表L的长度加1。
    6. 删除DeleteList(L,i),删除表L的第i个元素,表L的长度加1。

2. 线性表的顺序存储和基本运算实现

2.1 线性表的顺序存储

线性表的顺序存储指的是将线性表的数据元素按其逻辑次序依次存入一组地址连续的存储单元里,用这种方法存储的线性表称为顺序表。由于其在存储单元里也是连续的,所以每个元素结点a~i~的存储地址是该元素结点在表中的位置i的线性函数,只要确定了线性表存储的起始位置,线性表中任何一个元素都可以随机存取,所以顺序表是一种随机存取结构。

定义元素结点的代码:

#define ListSize 100 //表空间的大小应根据实际需要来定义,这里假设为100
typedef int DataType; //DataType的类型可根据实际情况而定,这里假设为int
typedef struct
{
    DataType data[ListSize];  //数组data用来存放表结点
    int lenght;  //线性表的当前表长(实际存储元素的个数);
} SeqList;

2.2 顺序表的基本运算实现

在顺序表上,有些运算是很容易实现的。例如,若表L是SeqList类型的顺序表,那么随机存储表中第i个结点,只需要用L.data[i - 1]就可以直接访问;若需要置空或者初始化表,仅需要将当前表长置成0,即L.length=0。因此下面仅列出顺序表的插入和删除两种基本运算的实现。

  1. 顺序表的插入运算 线性表的插入运算是指在线性表的第i-1个元素和第i个元素之间插入一个新元素x,使长度为n的线性表: ::: hljs-center

(a~1~, a~2~, ... , a~i-1~, a~i~, ... , a~n~)

:::

变为长度为n + 1的线性表: ::: hljs-center

(a~1~, a~2~, ... , a~i-1~, x, a~i~, ... , a~n~)

:::

由于线性表逻辑上相邻的元素在物理结构上也是相邻的,因此在插入一个新元素之后,线性表的逻辑关系发生了变化,其物理存储也要发生相应的变化。除非i等于n+1,否者必须将原线性表的第i,i+1,... ,n这些个元素分别向后移动一个位置,空出第i个位置以便插入新元素x。 插入算法实现如下:

void InsertList(SeqList *L, int i, DataType x)
{
    int j;
    if (i < 1 || i > L->lenght + 1)
    {
        printf("position error");
        return;
    }

    if (L->lenght >= ListSize)
    {
        printf("overflow   ");
        return;
    }

    for (j = L->lenght - 1; j >= i - 1; j--)
    {
        L->data[j + 1] = L->data[j];
    }
    L->data[i - 1] = x;
    L->lenght++;
}
  1. 顺序表的删除运算 线性表的删除运算指的是将表中第i(1 ≦ i ≦ n)个元素删除,与插入运算相反,插入是向后移动元素,而删除则是向前移动元素,除非i=n时,直接删除终端元素,不需要移动元素。 删除算法实现如下:
DataType DeleteList(SeqList *L, int i)
{
    int j;
    DataType x;
    if (i < 1 || i > L->lenght)
    {
        printf("position error");
        return -1;
    }

    x = L->data[i];
    for (j = i; j < L->lenght; j++)
    {
        L->data[j - 1] = L->data[j];
    }
    L->lenght--;
    return x;
}

3. 线性表的链式存储结构

线性表顺序存储结构的特点是,在逻辑关系上相邻的两个元素在物理位置上也是相邻的,因此可以随机存储任一元素。但是,当经常需要做插入和删除操作运算时,需要移动大量的元素,运行效率相对较低,而采用链式存储结构就可以避免这些移动。然而,由于链式存储结构存储线性表数据元素的存储空间可能时连续的,也可能是不连续的,因此链表的结点是不可以随机存取。

3.1 单链表(线性链表)

在使用链式存储结构表示每个数据元素a~i~时,除了存储a~i~本身的信息以外,还需要一个存储指示其后继元素a~i+1~存储位置的指针,它包括两个域,存储数据元素的域称为数据域,存储直接后继存储地址的域称为指针域。利用这种存储方式表达的线性表称为链表,由于这种链表的每个结点中只包含一个指针域,因此又称为单链表。

定义链表存储结构:

typedef char DataType;
typedef struct node {  //结点类型定义
    DataType data;   //结点的数据域
    struct node * next;   //结点的指针域
} ListNode;
typedef ListNode * LinkedList;
ListNode * p;     //定义一个指向结点的指针变量
LinkedList head;  //定义指向单链表的头指针
LinkedList rear;  //定义指向单链表的尾指针

3.2 单链表上的基本运算

1. 建立单链表

动态建立单链表的常用方法有两种,一种时头插法,另一种是尾插法。

  • 头插法建表 头插法建表是从一个空表开始,重复读入数据,生成新结点,将读入的数据存放到新节点的数据域中,然后将新结点插入到当前链表的表头上,直到读入结束标志为止。其具体算法如下:
void createListF()
{
    char ch;
    head = NULL;       // 置空单链表
    ch = getchar();    // 读入第一个字符
    while (ch != '\n') // 读入字符不是结束标志时作循环
    {
        p = (ListNode *)malloc(sizeof(ListNode)); // 申请新结点
        p->data = ch;                             // 数据域赋值
        p->next = head;                           // 指针域赋值
        head = p;                                 // 头指针指向新结点
        ch = getchar();                           // 读入下一个字符
    }
}
  • 尾插法建表 头插法建立链表是将新结点插入在表头,算法比较简单,但是新建链表结点的次序和输入时的顺序相反,理解时不太直观。若需要和输入次序一致,则可使用尾插法建立链表。该方法是将新结点插入在当前链表的表尾上,因此需要增设一个尾指针rear,使其始终指向链表中的尾结点。其具体算法如下:
void createListR()
{
    char ch;
    head = NULL; // 置空单链表
    rear = NULL;
    ch = getchar();    // 读入第一个字符
    while (ch != '\n') // 读入字符不是结束标识符时作循环
    {
        p = (ListNode *)malloc(sizeof(ListNode)); // 申请新结点
        p->data = ch;                             // 数据域赋值
        if (head == NULL)
        {
            head = p; // 新结点*p插入空表
        }
        else
        {
            rear->next = p; // 新结点*p插入到非空表的表尾结点*rear之后
        }
        rear = p;       // 尾指针指向新的表尾结点
        ch = getchar(); // 读入下一个字符
    }
    if (rear != NULL)
    {
        rear->next = NULL; // 终端结点指针域置空
    }
}

为了简化算法,方便操作,可在链表的开始结点之前附加一个结点,并称之为头结点。尾插法建立单链表的算法可以简化如下:

void createListR1()
{
    head = (ListNode *)malloc(sizeof(ListNode)); // 申请头结点
    head->data = 't';
    DataType ch;
    rear = head; // 尾指针初始指向头结点
    while ((ch = getchar()) != '\n')
    {
        p = (ListNode *)malloc(sizeof(ListNode)); // 申请新结点
        p->data = ch;
        rear->next = p; // 新结点链接到尾结点之后
        rear = p;       // 尾指针指向新结点
    }
    rear->next = NULL; // 终端结点指针域置空
}
2. 查找运算(带头结点)

在单链表中,任何两个结点的存储位置之间没有固定的联系,每个结点的存储位置包含在其直接前趋的指针域中。因此,在单链表中存取第i个结点时,必须从表头结点开始搜索,所以链表结构是非随机存取的存储结构。

  • 按结点序号查找 在单链表中要查找第i个结点,就必须从链表的第1个结点开始(下标为1),下标为0的是头结点。
ListNode *getNodeIndex(LinkedList head, int i)
{
    // head为带头结点的单链表的头指针,i为要查找的结点序号
    // 若查找成功,则返回查找结点的存储地址(位置),否则返回NULL
    ListNode *p;
    int j;
    p = head->next; // 使p指向第一个结点(开始结点)
    j = 1;          // j置1
    while (p != NULL && j < i)
    {
        p = p->next;
        j++;
    }
    if (i == j)
    {
        return p;
    }
    else
    {
        return NULL;
    }
}
  • 按结点值查找 在单链表中按值查找结点,就是从链表的开始结点出发,顺着链逐个将结点的值和给定值K进行比较,若遇到相等的值,则返回该结点的位置,否则返回NULL。具体算法如下:
ListNode *locateNodeK(LinkedList head, DataType k)
{
    // head为带头结点的单链表的头指针,k为要查找的结点值
    // 若查找成功,则返回查找结点的存储地址(位置), 否则返回NULL
    ListNode *pointer = head->next; // p指向开始结点
    while (pointer && pointer->data != k) // 循环直到pointer等于NULL或pointer->data等于k为止
    {
        pointer = pointer->next; // 指针指向下一个结点
    }
    return pointer; // 若找到值为K的结点,则p指向该结点,否者p为NULL
}
3. 插入运算

与顺序表不同,链表的插入不需要移动结点,但是需要移动指针来进行位置查找。假如需要插入一个数据x到a~i-1`~与a~i~之间,先使p指向a~i-1~的位置,然后生成一个数据域的值为x的新节点*s,再进行插入操作。具体算法如下:

void insertList(LinkedList head, int i, DataType x)
{
    // 在以head为头指针的带头结点的单链表中第i个结点的位置上
    // 插入一个数据域值为x的新结点
    ListNode *pointer, *s;
    int j;
    pointer = head;
    j = 0;
    while (pointer != NULL && j < i - 1) // 使pointer指向第i-1个结点
    {
        pointer = pointer->next;
        j++;
    }
    if (pointer == NULL)
    {
        printf("the insert position is error!");
        return;
    }
    else
    {
        s = (ListNode *)malloc(sizeof(ListNode)); // 申请新结点
        s->data = x;
        s->next = pointer->next;
        pointer->next = s;
    }
}
4. 删除运算

删除运算就是将链表的第i个结点从表中删去。由于第i个结点的存储地址是存储在第i-1个结点的指针域next中,因此要先使p指向第i-1个结点,然后使得p->next指向第i +1个结点,再将第i个结点释放掉。其具体算法如下:

DataType deleteList(LinkedList head, int i)
{
    // 在以head为头指针的带头结点的单链表中删除第i个结点
    ListNode *p, *s;
    DataType x;
    int j;
    p = head;
    j = 0;
    while (p != NULL && j < i - 1) // 使p指向第i-1个结点
    {
        p = p->next;
        j++;
    }
    if (p == NULL)
    {
        printf("delete position error!");
        exit(0);
    }
    else
    {
        s = p->next;       // s指向第i个结点
        p->next = s->next; // 使p->next指向第i+1个结点
        x = s->data;       // 保存被删除的值
        free(s);           // 释放第i个结点的内存
        return x;          // 返回结点值
    }
}

3.3 循环链表

循环链表是链式存储结构的另一种形式。其特点是单链表中最后一个结点(终端结点)的指针域不为空,而是指向链表的头结点,使整个链表构成一个环。因此,从表中任意一个结点开始都可以访问表中其他结点,这种结构形式的链表称为单循环链表。循环链表的结点类型与单链表完全相同,在操作上也与单链表基本一致,差别仅在于算法中循环结束的判断条件不再是p或者p->next是否为空,而是它们是否等于头指针.

3.4 双向链表

单链表和单循环链表的结点中只设有一个指向其直接后继的指针域,因此,从某个结点出发只能顺指针向后访问其他结点。若需要查找结点的直接前趋,则需要从头指针开始查找某节点的直接前趋结点。若希望从表中快速确定一个结点的直接前趋,只要在单链的结点类型中增加一个指向其直接前趋的指针域prior即可。这样形成的链表中有两条不同方向的链,因此称为双向链表。双向链表及其结点类型描述如下:

typedef char DataType;
typedef struct dlnode {
    DataType data;
    struct dlnode *prior, *next;
} DLNode;
typedef DLNode * DLinkList;
DLinkList head;

为了某些操作运算的方便,双链表也增设一个头结点,若将尾结点和头结点链接起来也就构成了循环链表。

  • 插入运算
void DLInsert(DLNode *p, DataType x)
{
    //将值为x的新结点插入到到带头结点的双向链表中指定结点*p之前
    DLNode *s = (DLNode *)malloc(sizeof(DLNode));  //申请新结点
    s->data = x;
    s->prior= p->prior;
    s->next = p;
    p->prior->next = s;
    p->prior = s;
}
  • 删除运算
DataType DLDelete(DLNode *p)
{
    //删除带头结点的双向链表中指定的结点*p
    p->prior->next = p->next;
    p->next->prior = p->prior;
    x = p->data;
    free(p);
    return x;
}

标签:结点,线性表,head,next,链表,指针
From: https://blog.51cto.com/u_12116049/6049745

相关文章

  • 王道视频-数据结构-笔记2:线性表
    文章目录0笔记说明1线性表1.1线性表的定义1.2线性表的基本操作2顺序存储的线性表:顺序表2.1静态分配的顺序表2.2动态分配的顺序表2.3顺序表的特点2.4顺序表的基本操......
  • 线性表的顺序存储
    一、线性表简介  线性表是一种线性结构,它是由零个或多个数据元素构成的有限序列。线性表的特征是在一个序列中,除了头尾元素,每个元素都有且只有一个直接前驱,有且只有一个直......
  • 线性表的链接储存结构及实现
    单链表1单链表的定义单链表是用一组任意的存储单元来存放线性表的元素。存储单元可以连续,也可以不连续为了正确的表示元素之间的逻辑关系,每个存储单元除了存储元素外,还需......
  • 线性表的顺序存储
    线性表的顺序存储线性表的顺序表示又称为顺序存储结构或顺序映像顺序存储:把逻辑上相邻的数据元素(类型相同)存储在物理上相邻(中间没有空出存储单元,占用一片连续的存储空间)......
  • 线性表
    线性表线性表是具有相同特性的数据元素的一个有限序列;是一种典型的线性结构线性表的两种存储结构:顺序存储结构链式存储结构抽象数据类型线性表抽象数据类型线性......
  • 线性表之堆栈
    什么是堆栈像叠盘子一样,先放下的在下面,先拿出来的却是最上面的,也就是,先进去的最后才出来先进后出的就是堆栈堆栈的操作生成空堆栈,其最大长度为MaxSize判断堆栈S是......
  • 线性表之队列
    目录什么是队列大众化专业性队列的操作集队列的链式存储实现链表结构体初始化删除并返回队头数据元素其他操作什么是队列大众化最常见的队列就是排队假设超市送鸡蛋......
  • 数组描述线性表(C++实现)
    线性表也称有序表,其每一个实例都是元素的一个有序集合抽象类linearList一个抽象类包含没有实现代码的成员函数,这样的成员函数称为纯虚函数,用数字0作为初始值来说明templ......
  • 第二章 线性表(上)
    一、线性表的定义及具体操作1.定义线性表(LinearList)是具有相同数据类型的n(n≥0)个数据元素的有限序列,其中n为表长,当n=0时线性表是一个空表。若用L命名线性表,则其一般表......
  • 线性表算法相关练习
    //将两个递增的有序链表合并为一个递增的有序链表。//要求结果链表仍使用原来两个链表的存储空间,不另外占用其他的存储空间。表中不允许有重复的数据。#include<iostream>#......