数据结构与算法--线性表

02 线性表

2.1 线性表的逻辑结构

线性表的特点

在数据元素的非空有限集中:

  • 存在唯一一个被称为“第一个”的数据元素;

  • 存在唯一一个被称为“最后一个”的数据结构;

  • 除第一个外,每个数据元素只有一个前驱;

  • 除最后一个外,每个数据元素只有一个后继。

2.2 线性表的顺序存储结构

用一组 地址连续 的存储单元存放的线性表称为顺序表。可采用一维数组或动态分配顺序存储结构的方式实现,对任一数据元素均 可随机存取

(1)顺序表初始化

#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0
#define INFEASIBLE -1
#define OVERFLOW -2

typedef int Status;

//*******************************
// 定义顺序表
#define LIST_INIT_SIZE 100  // 线性表初始容量
#define LISTINCREMENT  10   // 分配增量
typedef int ElemType;  // 元素类型

typedef struct 
{
    ElemType *elem;    // 线性表首地址
    int length;        // 当前线性表的长度
    int listSize;      // 最大容量
} SqList;


// *******************************
// 线性表初始化
Status InitList_Sq(SqList &L)
{
    L.elem = (ElemType*) malloc(LIST_INIT_SIZE * sizeof(ElemType));
    if (! L.elem)
        exit(OVERFLOW);   // 判断是否申请成功
    L.length = 0;   // 初始长度
    int listSize = LIST_INIT_SIZE;   // 初始容量
}

(2)动态分配函数

  • 申请内存函数
void* malloc(unsigned size)

申请长度为 size 个字节的空间,返回内存空间的首地址,申请失败则返回 NULL,注意需要将返回值进行强制转换。

  • 释放内存函数
void free(void *p);

释放以 p 为首地址的内存空间,但释放的空间必须是 malloc() 申请的。

例如

// 申请长度为 n 的整型数组空间
int *p, n;
scanf("%d", &n);
p = (int*) malloc(n * sizeof(int));  强制转换为 int 型指针
...
free(p);

(3)查找:

在线性表 L 中查找是否存在数据元素 e,返回第 1 个值与 e 相同的元素的位序,若不存在则返回 0。

int LocationElem(Sqlist L, ElemType e)
{
    int i = 1;    // 位序,初始为 1
    ElemType *p = L.elem;    // 从第一个元素位置开始查找
    while(*p != e && i<=L.length)
    {
        p++;
        i++
    }
    if(i<=L.length)  // 判断是否存在
        return i;
    else
        return 0;
}

(4)插入:

将 e 插入到 $\mathrm{a_i}$,将 $\mathrm{a_i}$~$\mathrm{a_n}$ 的元素一次后移。若空间不足,则修改已分配内存区的大小。

void* realloc(void* p, unsigned size) 

p 指向的内存空间大小改为 size,可以比原来空间大或小。若原存储空间不足,分配另一个足够大的存储空间,将原内容复制进去。申请成功,返回首地址;否则返回 NULL。

Status ListInsert(SqList &L, int i, ElemType e)
{
    ElemType *p, *q, *newbase;
    if(i<1 || i>L.length+1)   // 判断插入位置 i 是否合法
        return (ERROR);
    
    if(L.length>=L.listSize)   // 容量不足。扩大容量
    {
        newbase = (ElemType*) realloc(L.elem, (L.listSize+LISTINCREMENT) * sizeof(ElemType));
        if(!newbase) 
            exit(OVERFLOW);
        L.elem = newbase;
        L.listSize += LISTINCREMENT;
    }
    
    p = &(L.elem[L.length-1]);   // 指向末尾元素
    q = &(L.elem[i-1]);          // 指向插入位置
    for(; p>=q; p--)             // 元素后移
        *(p+1) = *p;
    *q = e;                     // 插入
    ++L.length;                 // 表长加 1
    return OK;
}

(5)删除:
删除 a i \mathrm{a_i} ai,将 a i + 1 \mathrm{a_{i+1}} ai+1~ a n \mathrm{a_n} an 依次前移。

Status ListDelete(SqList &L, int i, ElemType &e)
{
    ElemType *p, *q;
    if(i < 1 || i > L.length)
        return ERROR;
    
    p = &(L.elem[i-1]);    // 记录删除位置
    e = *p;                // 记录删除元素
    q = L.elem + L.length - 1;    // 记录表尾位置
    for(++p; p<=q; ++p)
        *(p-1) = *p;
    
    --L.length;      // 表长度减一
    return OK;
}

2.3 线性表的链式存储结构

每个节点包括数据域和指针域,数据域中存储元素本身信息,指针域存储结点直接后继的地址。

2.3.1 单链表

(1)为便于操作,在第一个结点之前附设一个头结点。链表节点的定义

在这里插入图片描述

typedef struct node
{
    ElemType data;
    struct node* next;
}Lnode, *LinkList;

(2)查找:

查找单链表中是否存在数据域为 e 的结点,若有则返回该结点的指针;否则返回 NULL。

LiskList LocationElem_L(LinkList L, ElemType e)
{
    LinkList p = L->next;     // 指向第一个结点的地址
    while(p!=NULL && p->data != e)
        p = p->next;
    return p;
}

复杂度:T(n)=O(n)

(3)取元素值

取单链表中第 i 个元素的值,若存在该元素,用 e 返回其值;否则返回 ERROR。

Status GetElem_L(LinkList L, int i, ElemType &e)
{
    LinkList p = L->next;
    int j = 1;    // 从第一个结点开始查找
    while(p && j<i)
    {
        p = p-next;
        j ++;
    }
    
    if(p==NULl || i<j)  // 判断跳出循环原因
        return ERROR;
    e = p->data;
    return OK;
}

复杂度:T(n)=O(n)

(4)插入

在单链表 L 的第 i 个元素之前插入新元素 e。

先找到第 i-1 个元素,然后用结点指针连接。

Status ListInsert_L(LinkList &L, int i, ElemType e)
{
    LinkList p = L, s; 
    int j = 0;
    while(p && j<i-1)   // 查找 i-1 元素
    {
        p = p->next;
        j++;
    }
    if(p==NULL || j>i-1)
        return ERROR;
    
    // 找到 i-1 后插入结点
    s = (LinkList)malloc(sizeof(Lnode);   // 申请新结点空间
    s->data = e;
    s->next = p->next;
    p->next = s;
    return OK;
}

复杂度:T(n)=O(n)

(5)删除:

删除单链表 L 的第 i 个结点并记录。还是先找到第 i-1 个结点,将第 i 个结点释放。

Status ListDelete_L(LinkList &L, int i, ElemType &e)
{
    LinkList p = L, q;
    int j = 0;
    while(p && j<i-1)
    {
        p = p->next;
        j++
    }
    if(p==NULL || j>i-1)
        return ERROR;
    
    q = p->next;   // p 指向第 i 个结点
    p->next = q->next;
    e = q->data;
    free(q);
    return OK;
}

复杂度:T(n)=O(n)

(6)动态建立单链表

输入 n 个数据,建立单链表 L。

① 头插法:在头结点和第一个结点之间插入新结点,逆序 输入 n 个元素。

void Create_L1(LinkList &L, int n)
{
    LinkList p;
    int i;
    // 创建头结点
    L = (LinkList)malloc(sizeof(Lnode);
    L->data = 0;
    L->next = NULL;
    
    for(i=0; i<n; i++)
    {
        p = (LinkList)malloc(sizeof(Lnode);  // 创建新结点
        scanf("%d", &p->data);
        p->next = L->next;     // 将 p 插入为头结点的后继
        L->next = p;
    }
}

复杂度:T(n)=O(n)

② 尾插法:在单链表尾插入新结点,顺序输入 n 个元素。

void Create_L2(LinkList &L, int n)
{
    LinkList p, s;
    int i;
    L = (LinkList)malloc(sizeof(Lnode);
    L->data = 0;
    L->next = NULL;
    s = L;
    
    for(i=0; i<n; i++)
    {
        p = (LinkList)malloc(sizeof(Lnode);
        scanf("%d", &p->data);
        p->next = NULL;
        s->next = p;     // 将 p 插入为 s 的后继
        s = p;           // 使 s 始终指向表尾
    }
}

复杂度:T(n)=O(n)

(7)单链表适用于经常对数据进行插入/删除的操作的问题,不适合经常需要随机存取的问题。

2.3.2 循环链表

循环链表:表中最后一个结点的指针域指回头结点的链表。

在这里插入图片描述

从表中任一结点出发均可找到其他结点。与单链表的区别为在于算法的循环条件不同:

// 单链表
while(p!=NULL)

// 循环链表
while(p!=L)

2.3.3 双向链表

表中结点有 前驱指针域后继指针域的链表。双向链表具有对称性。

typedef struct Dunode
{
    struct Dunode *prior, *next;     // 指针域
    ElemType element;                // 数据域
}DuLnode, *DuLinkList;

在这里插入图片描述

(1)删除:删除双向链表中指针 p 指向的结点

void del_dulist(DuLinkList p)
{
    p->prior->next = p->next;
    p->next->prior = p->prior;
    free(p);
}

复杂度:T(n)=O(n)

(2)插入:在双向链表中指针 p 指向的结点前插入新结点(注意每一步的顺序)。

void ins_dulist(DuLinkList p, ElemType e)
{
    DuLinkList s;          // 新结点
    s = (DuLinkList) malloc(sizeof(DuLnode));
    s->element = e;
    s->prior = p->prior;
    p->prior->next = s;
    s->next = p;
    p->prior = s;
}

复杂度:T(n)=O(n)

2.3.4 静态链表

借助一维数组描述链式结构,使用游标模拟指针。每一个结点包括数据域和指针域,指针域的值为下一节点在数组中的位置(序号)。

静态链表的插入和删除操作不需要移动元素,仅需要修改“游标”即可。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值