王道计算机考研 数据结构 (绪论、线性表)

第一章、绪论

1.1_数据结构的基本概念

知识总览

在这里插入图片描述

数据元素、数据项

  • 数据元素是数据的基本单位,通常作为一个整体考虑
  • 一个数据元素可由若干个数据项组成,数据项是构成数据元素的不可分割的最小单位
  • 在这里插入图片描述

数据结构、数据对象

  • 数据结构是相互之间存在一种或多种特定关系数据元素的集合
  • 数据对象是具有相同性质的数据元素的集合,是数据的一个子集
  • 在这里插入图片描述

数据结构的三要素

在这里插入图片描述

  • 逻辑结构集合 :各个元素同属于一个集合,别无其它关系; 线性结构 :数据元素之间是一对一的关系,除了第一个元素,每一个元素都有唯一前趋,除了最后一个元素,每一个元素都有唯一后驱;树形结构 :数据元素之间是一对多的关系;图结构 :数据元素之间是多对多的关系。

  • 物理结构(存储结构) :
    在这里插入图片描述
    在这里插入图片描述

  • 数据的运算 :运算的定义是针对逻辑结构的,运算的实现是针对存储结构的。

数据类型、抽象数据类型

  • 数据类型是一个值的集合和定义在这个集合上的一组操作的总称。
    在这里插入图片描述
  • 抽象数据类型(ADT)是抽象数据组织及与之相关的操作。也就是在数据结构三要素中只关心逻辑结构和数据的运算,而不关心物理结构(存储结构)

1.2.1_算法的基本概念

在这里插入图片描述

什么是算法

  • 程序 = 数据结构 + 算法
  • 数据结构是要处理的信息
  • 算法是处理信息的步骤

算法的特性

  • 算法特性是算法必须具备的特性
  • 1.有穷性 :一个算法总在有穷步之后结束,且每一步会在有穷时间内完成。
  • 注 :算法必须是有穷的,而程序可以是无穷的。
  • 2.确定性 :对于相同的输入只能得到相同的输出
  • 3.可行性 :算法中描述的操作都可以通过已经实现的基本运算执行有限次实现。
  • 4.输入 :有零个或多个输入。
  • 5.输出 :有一个或多个输出。

1.2.2_算法的时间复杂度

在这里插入图片描述

  • 一般情况下我们只看一个算法的最坏时间复杂度和平均时间复杂度
  • 如何计算 :找到一个基本操作(最深层循环)

1.2.3_算法的空间复杂度

在这里插入图片描述

  • 无论问题规模怎么变,算法运行所需的内存空间都是固定的常量 O ( 1 ) O(1) O(1),算法原地工作

在这里插入图片描述

  • 开一个与问题规模n有关的一维数组

函数递归调用带来的内存开销

  • 空间复杂度 与 递归调用的深度 有关

第二章、线性表

2.1_线性表的定义和基本操作

  • 线性表是具有相同数据类型(每个数据元素所占空间一样大)的n个数据元素有限 序列(序 :有次序)。
  • 注意 :数据元素的位序从1开始,数组下标从0开始。用数组实现线性表时需要审题。

线性表的基本操作

在这里插入图片描述

  • 参数的引用“&”是C++中的,可以把对参数的修改结果“带回来”

在这里插入图片描述

2.2.1_顺序表的定义

  • 顺序表 :用顺序存储(逻辑上相邻的元素,物理上也相邻)的方式实现线性表

顺序表的实现-静态分配

#define MaxSize 10      // 定义最大长度
typedef struct{
    ElemType data[MaxSize];     // 用静态的“数组”存放数据元素
    int length;     // 顺序表的当前长度
}SqList;

#include <stdio.h>

#define MaxSize 10

typedef struct{
    int data[MaxSize];
    int length;
}SqList;

void InitList(SqList &L)
{
    // 可以省略,但可能由于遍历时用到MaxSize有脏数据,要用length遍历
    for (int i = 0; i < MaxSize; i ++ )
        L.data[i] = 0;
    
    L.length = 0;       // 不可省略,顺序表初始长度为0
}

int main()
{
    SqList L;		// 声明一个顺序表
    InitList(L);		// 初始化顺序表
    
    return 0;
}

顺序表的实现-动态分配

  • 这样就可以让顺序表的容量可变
  • 虽然动态分配可以使顺序表的大小可以灵活改变,但是时间开销还是比较大的(复制元素)
  • 注意malloc和free是一对函数
  • free函数会把p这个指针所指向的这一整片的存储空间给释放掉,归还给系统,然后由于p是局部于这个函数的变量,函数结束后,存储p这个变量的存储空间会被系统自动回收
#define InitSize 10     // 顺序表的初始长度
typedef struct
{
    ElemType *data;     // 指示动态分配数组的指针,这个指针指向顺序表中第一个数据元素
    int MaxSize;        // 顺序表的最大容量
    int length;     // 顺序表的当前长度
} SeqList;      // 顺序表的类型定义(动态分配方式)

// 动态申请和释放空间

// 在C语言中的函数分别是malloc和free函数
// malloc函数是申请一整片连续的内存空间,且会return一个指向这一整片存储空间开始地址的指针,需要强制转型为你定义的数据元素类型的指针
// L.data = (ElemType *)malloc(sizeof(ElemType) * InitSize);
// malloc和free包含在<stdlib.h>头文件中

// 在C++语言中分别是new和delete这两个关键字

在这里插入图片描述

#include <stdlib.h>

#define InitSize 10

typedef struct
{
    int *data;
    int MaxSize;
    int length;
} SeqList;

void InitList(SeqList &L)
{
    L.data = (int *)malloc(sizeof(int) * InitSize);
    L.MaxSize = InitSize;
    L.length = 0;
}

void IncreaseSize(SeqList &L, int len)
{
    int *p = L.data;
    L.data = (int *)malloc(sizeof(int) * (L.MaxSize + len));
    for (int i = 0; i < L.length; i ++ )
        L.data[i] = p[i];
    L.MaxSize = L.MaxSize + len;
    free(p);
}

int main()
{
    SeqList L;
    InitList(L);
    // ...往顺序表中随意随便插入几个元素
    IncreaseSize(L, 5);
    return 0;
}

顺序表的特点

  • 随机访问,即可以在 O ( 1 ) O(1) O(1)时间内找到第i个元素(不论是静态分配还是动态分配代码都是 d a t a [ i − 1 ] data[i - 1] data[i1]
  • 存储密度高,每个节点只存储数据元素(链表还要存指针)
  • 拓展容量不方便(即便采用动态分配的方式实现,拓展长度的时间复杂度也比较高)
  • 插入、删除操作不方便,需要移动大量元素

在这里插入图片描述

2.2.2.1_顺序表的插入删除

顺序表的插入

  • L i s t I n s e r t ListInsert ListInsert(&L, i, e) :插入操作,在表L中的第i个位置(位序)插入指定元素i
  • 本节代码建立在顺序表的“静态分配”实现方式之上,“动态分配”也雷同。
  • 时间复杂度的平均情况 : p = 1 / ( n + 1 ) p=1/(n+1) p=1/(n+1);i=1,循环n次,i=2,循环n-1次…;平均循环次数 = n p + ( n − 1 ) ∗ p + . . . + 1. p = n ∗ ( n + 1 ) / 2 ∗ 1 / ( n + 1 ) = n / 2 =np+(n-1)*p+...+1.p=n*(n+1)/2*1/(n+1)=n/2 =np+(n1)p+...+1.p=n(n+1)/21/(n+1)=n/2
#define MaxSize 10

typedef struct
{
    int data[MaxSize];
    int length;
} SqList;

bool ListInsert(SqList &L, int i, int e)
{
    if (i < 1 || i > L.length + 1)      // 判断i的范围是否有效
        return false;
    if (L.length == MaxSize)        // 当前存储空间已满,不能插入
        return false;

    for (int j = L.length; j >= i; j -- )       // 将第i个元素及之后的元素后移
        L.data[j] = L.data[j - 1];
    L.data[i - 1] = e;      // 在位置i处放e
    L.length ++ ;       // 长度加1
    
    return true;        // 反馈
}

int main()
{
    SqList L;
    InitList(L);
    // ...插入一些元素
    ListInsert(L, 5, 5);
    
    return 0;
}

顺序表的删除

  • L i s t D e l e t e ListDelete ListDelete(&L, i, &e) :删除操作,删除表L中第i个位置的元素,并用e返回删除元素的值
  • 因为要返回e,所以这要有一个引用操作,因此,在这个函数中操作的变量e,在内存中其实对应的是同一份数据
  • 在删除操作中是先移动前面的元素再移动后面的元素,而在插入操作中要把元素往后移时,先把后面的元素往后移,然后再移前面的元素
bool ListDelete(SqList &L, int i, int &e)
{
    if (i < 1 || i > L.length)      // 判断i的范围是否有效
        return false;
    
    e = L.data[i - 1];      // 将被删除的元素赋给e
    
    for (int j = i; j < L.length; j ++ )        // 将第i个位置后的元素前移
        L.data[j - 1] = L.data[j];
    
    L.length -- ;     // 线性表长度减一
    
    return true;
}


int main()
{
    SqList L;
    InitList(L);
    // ...插入一些元素
    
    int e = -1;     // 用变量e把删除的元素“带回来”
    
    if (ListDelete(L, 3, e))
        printf("已删除第3个元素,删除元素值为=%d\n", e);
    else
        printf("位序i不合法,删除失败");
}

在这里插入图片描述

2.2.2.2_顺序表的查找

顺序表的按位查找

  • G e t E l e m GetElem GetElem(L, i) :按位查找操作。获取表L中第i个位置的元素的值
  • O ( 1 ) O(1) O(1)
// 静态分配实现顺序表,动态分配实现的顺序表也是如此
ElemType GetElem(SqList L, int i)
{
    // 判断i合法性
    
    return L.data[i - 1];
}

顺序表的按值查找

  • L o c a t e E l e m LocateElem LocateElem(L, e) :按值查找操作,在表L中查找具有给定关键字值的元素
#define InitSize 10
typedef struct
{
    ElemType *data;
    int MaxSize;
    int length;
} SeqList;

ElemType LocateElem(SeqList L, ElemType e)
{
    for (int i = 0; i < L.length; i ++ )
        if (L.data[i] == e)
            return i + 1;		// 返回位序
    return 0;
}

结构类型的比较

  • 在C语言中,结构类型的比较不能直接用“==”,需要依次对比各个分量来判断两个结构体是否相等;如果C++,则可以用重载 “= =“。
  • 但是,《数据结构》考研初试中,手写代码可以直接用“= =”,无论ElemType是基本数据类型还是结构类型。但是有的学校考《C语言程序设计》,那么也许语言就要严格一些。最好还是看一下相关的历年真题。

2.3.1_单链表的定义

用代码定义一个单链表

struct LNode        // 定义单链表节点类型
{
    ElemType data;      // 每个节点存放一个数据元素
    struct LNode *next;     // 指针指向下一个节点
};

struct LNode *p = (struct LNode *)malloc(sizeof(struct LNode));      // 增加一个新的节点 :在内存中申请一个节点需要的空间,并用指针p指向这个节点

  • 这样写每次都要有 s t r u c t struct struct有些麻烦,所以教材中使用了 t y p e d e f typedef typedef关键字(C语言),可以把数据类型重命名
  • t y p e d e f < 数 据 类 型 > < 别 名 > typedef<数据类型><别名> typedef<><>
  • 这样就可以变成 :
struct LNode        // 定义单链表节点类型
{
    ElemType data;      // 每个节点存放一个数据元素
    struct LNode *next;     // 指针指向下一个节点
};

typedef struct LNode LNode;
LNode *p = (LNode *)malloc(sizeof(LNode));

  • 教材中还有一种更简洁的方式
typedef struct LNode    // 定义单链表节点类型
{
    ElemType data;
    struct LNode *next;
} LNode, *LinkList;

// LinkList - 单链表



// 上面这种写法等价于 :



struct LNode
{
    ElemType data;
    struct LNode *next;
}

typedef struct LNode LNode;
typedef struct LNode *LinkList;

  • 要表示一个单链表时,只需声明一个头指针L,指向单链表的第一个结点
// 声明一个指向单链表第一个结点的指针



LNode *L;

// 等价于

LinkList L;		// 代码可读性更强,详见下面例子中,

// GetElem函数的LNode *和LinkList虽然两者时等价的
//但在这个函数中它最终要返回的是第i个结点,所以把返回值的类型定义为LNode *,
// 其实它LNode *就是想强调返回的是一个结点,而LinkList想强调这是一个单链表
typedef struct LNode
{
    ElemType data;
    struct LNode *next;
}LNode, *LinkList;

LNode *GetElem(LinkList L, int i)
{
    int j = 1;
    LNode *p = L -> next;
    
    if (i == 0)
        return L;
    if (i < 1)
        return NULL;
    
    while (p != NULL && j < i)
    {
        p = p -> next;
        j ++ ;
    }
    
    return p;
}

  • 强调这是一个单链表 - 使用LinkList
  • 强调这是一个结点 - 使用LNode *

不带头结点的单链表

typedef struct LNode
{
    ElemType data;
    struct LNode *next;
}LNode, *LinkList;

bool InitList(LinkList &L)      // 注意传入引用
{
    L = NULL;       // 空表,暂时还没有任何结点   防止脏数据!!!
    return true;
}

void test()
{
    LinkList L;     // 声明一个指向单链表的指针     注意,此处并没有创建一个结点!!!
    // 初始化一个空表
    InitList(L);
}

// (不带头结点)
bool Empty(LinkList L)
{
    if (L == NULL)
        return true;
    else
        return false;
}

// 或者

// (不带头结点)
bool Empty(LinkList L)
{
    return (L == NULL);
}

带头结点的单链表

typedef struct LNode
{
    ElemType data;
    struct LNode *next;
}LNode, *LinkList;


// 初始化一个单链表(带头结点)
bool InitList(LinkList &L)
{
    L = (LNode *)malloc(sizeof(LNode));     // 分配一个头结点
    if (L == NULL)      // 内存不足,分配失败
        return false;
    
    L -> next = NULL;       // 头节点之后暂时还没有结点
    return true;
}

void test()
{
    LinkList L;     // 声明一个指向单链表的指针
    InitList(L);        // 初始化一个空表
}


// 判断单链表是否为空(带头节点)
bool Empty(LinkList L)
{
    if (L -> next == NULL)
        return true;
    else
        return false;
}

  • 如果不带头结点 :头指针所指向的下一个结点就是实际用于存放数据的结点;而如果带头结点的话 :头指针所指向的这个结点把它称为头结点,这个头结点是不存放实际数据元素的,只有这个头结点之后的下一个结点才用于存放数据

在这里插入图片描述

2.3.2.1_单链表的插入删除

按位序插入(带头结点)

  • L i s t I n s e r t ListInsert ListInsert(&L, i, e) :插入操作,在表L中的第i个位置上插入指定元素e
  • i是位序
  • 找到第i-1个结点,将新结点插入其后
  • 头结点可以看成第0个结点,所以当i=1时,也适用分析逻辑
  • 在这里插入图片描述
// 在第i个位置插入元素e(带头结点)
bool ListInsert(LinkList &L, int i, ElemType e)
{
    if (i < 1)
        return false;
    
    LNode *p = L;       // 指针p指向当前扫描到的结点
    int j = 0;      // 当前p指向的是第几个结点
    
    while (p != NULL && j < i - 1)      // 循环找到第i-1个结点
    {
        j ++ ;
        p = p -> next;
    }
    
    if (p == NULL)      // i值不合法
        return false;
    
    LNode *s = (LNode *)malloc(sizeof(LNode));
    s -> data = e;
    s -> next = p -> next;
    p -> next = s;
    
    return true;
}

按位序插入(不带头结点)

  • L i s t I n s e r t ListInsert ListInsert(&L, i, e) :插入操作,在表L中的第i个位置上插入指定元素e
  • 找到第i-1个结点,将新结点插入其后
  • 不存在“第0个”结点,因此i=1时需要特殊处理
  • 不带头结点,则插入、删除第1个元素时,需要更改头指针L

在这里插入图片描述

bool ListInsert(LinkList &L, int i, ElemType e)
{
    if (i < 1)
        return false;
    
    if (i == 1)
    {
        LNode *s = (LNode *)malloc(sizeof(LNode));
        s -> data = e;
        s -> next = L;
        L = s;
        
        return true;
    }
    
    LNode *p = L;
    int j = 1;      // 注意这里是1!!!不带头结点
    
    while (p != NULL && j < i - 1)
    {
        p = p -> next;
        j ++ ;
    }
    
    if (p == NULL)
        return false;
    
    LNode *s = (LNode *)malloc(sizeof(LNode));
    s -> data = e;
    s -> next = p -> next;
    p -> next = s;
    
    return true;
}

  • 不带头结点写代码更不方便,因此之后代码默认使用带头结点,但考试中两种情况都可能考察

指定结点的后插操作

在这里插入图片描述

bool InsertNextNode(LNode *p, ElemType e)
{
    if (p == NULL)
        return false;
    
    LNode *s = (LNode *)malloc(sizeof(LNode));
    
    if (s == NULL)      // 内存分配失败,考试可以不写
        return false;
    
    s -> data = e;
    s -> next = p -> next;
    p -> next = s;
    
    return true;
}

在这里插入图片描述

指定结点的前插操作

在这里插入图片描述
在这里插入图片描述

// 前插操作 :在p结点之前插入元素e
// O(1)
bool InsertPriorNode(LNode *p, ElemType e)
{
    if (p == NULL)
        return false;
    
    LNode *s = (LNode *)malloc(sizeof(LNode));
    
    if (s == NULL)
        return false;
    
    s -> next = p -> next;
    p -> next = s;
    s -> data = p -> data;
    p -> data = e;
    
    return true;
}

// 王道书版本

bool InsertPriorNode(LNode *p, LNode *s)
{
    if (p == NULL || s == NULL)
        return false;
    
    s -> next = p -> next;
    p -> next = s;
    
    ElemType temp = p -> data;      // 交换数据域部分
    p -> data = s -> data;
    s -> data = temp;
    
    return true;
}

按位序删除(带头结点)

  • L i s t D e l e t e ListDelete ListDelete(&L, i, &e) :删除操作。删除表L中第i个位置的元素,并用e返回删除元素的值
  • 找到第i-1个结点,将其指针指向第i+1个结点,并释放第i个结点
bool ListDelete(LinkList &L, int i, ElemType &e)
{
    if (i < 1)
        return false;
    
    LNode *p = L;
    int j = 0;
    
    while (p != NULL && j < i - 1)
    {
        p = p -> next;
        j ++ ;
    }
    
    if (p == NULL)
        return false;
    if (p -> next == NULL)      // 第i-1个结点之后已无结点
        return false;
    
    LNode *q = p -> next;
    e = q -> data;
    p -> next = q -> next;
    free(q);
    
    return true;
}

指定结点的删除

  • 删除结点p,需要修改其前趋结点的next指针
  • 方法1 :传入头指针,循环寻找p的前趋结点
  • 方法2 :偷天换日(类似于结点前插的实现)
bool DeleteNode(LNode *p)
{
    if (p == NULL)
        return false;
    
    LNode *q = p -> next;
    p -> data = p -> next -> data;
    p -> next = q -> next;
    
    free(q);
    return true;
}

  • 但是!以上有BUG!如果p是最后一个结点,只能从表头开始依次寻找p的前趋,时间复杂度O(N),否则 p − > n e x t − > d a t a p -> next -> data p>next>data会出错;不过可能大概只扣一分

在这里插入图片描述

2.3.2.2_单链表的查找

  • 本节只探讨“带头结点”的情况

按位查找

  • G e t E l e m GetElem GetElem(L, i)
LNode * GetElem(LinkList L, int i)
{
    if (i < 0)
        return false;
    
    LNode *p = L;
    int j = 0;
    
    while (p != NULL && j < i)
    {
        p = p -> next;
        j ++ ;
    }
    
    return p;
}

  • 如果i=0,头结点
  • 如果i大于长度,返回NULL

在这里插入图片描述

按值查找

LNode * LocateElem(LinkList L, ElemType e)
{
    LNode *p = L -> next;
    while (p != NULL && p -> data != e)
        p = p -> next;
    return p;
}

求表的长度

int Length(LinkList L)
{
    LNode *p = L;
    int len = 0;
    while (p != NULL)
    {
        p = p -> next;
        len ++ ;
    }
    return len;
}

在这里插入图片描述

2.3.2.3_单链表的建立

尾插法建立单链表

  • 设置变量length记录链表长度,用ListInsert,O(n^2);设置一个表尾指针,只要每次对r指针进行后插操作InsertNextNode,然后把表尾指针往后移
// O(n)
LinkList List_TailInsert(LinkList &L)
{
    int x;
    L = (LinkList)malloc(sizeof(LNode));		// 建立头结点
    LNode *s, *r = L;
    
    scanf("%d", &x);
    while (x != 9999)
    {
        s = (LNode *)malloc(sizeof(LNode));
        s -> data = x;
        r -> next = s;
        r = s;
        scanf("%d", &x);
    }
    r -> next = NULL;
    return L;
}

头插法建立单链表

  • 对头结点的后插操作

在这里插入图片描述

在这里插入图片描述

2.3.3_双链表

双链表的初始化(带头结点)

typedef struct DNode
{
    ElemType data;
    struct DNode *next, *prior;
}DNode, *DLinkList;

// 初始化双链表
bool InitDLinkList(DLinkList &L)
{
    L = (DLinkList)malloc(sizeof(DNode));
    if (L == NULL)
        return false;
    L -> next = NULL;		// 头结点之后暂时还没有结点
    L -> prior = NULL;		// 头结点的prior指针永远指向NULL
    return true;
}

void testDLinkList()
{
    DLinkList L;
    InitDLinkList(L);
}

bool Empty(DLinkList L)
{
    if (L -> next == NULL)
        return true;
    else
        return false;
}


双链表的插入

// 在p结点之后插入s结点(后插)

bool InsertNextNode(DNode *p, DNode *s)
{
    if (p == NULL || s == NULL)
        return false;
    
    s -> next = p -> next;
    if (p -> next != NULL)      // 如果p结点有后继结点
        p -> next -> prior = s;
    s -> prior = p;
    p -> next = s;
    
    return p;
}

  • 以上是前插法,但由于是双链表,如果要前插,只要找到前一个用后插就可以

双链表的删除

// 删除p结点的后继结点
bool DeleteNextNode(DNode *p)
{
    if (p == NULL)
        return false;
    DNode *q = p -> next;
    if (q == NULL)
        return false;
    p -> next = q -> next;
    if (q -> next != NULL)
        q -> next -> prior = p;
    free(q);
    return true;
}

void DestroyList(DLinkList &L)
{
    while (L -> next != NULL)
        DeleteNextDNode(L);
    free(L);        // 释放头结点
    L = NULL;       // 头结点指向NULL
}

双链表的遍历

在这里插入图片描述

在这里插入图片描述

2.3.4_循环链表

循环双链表

在这里插入图片描述

typedef struct LNode        // 定义单链表结点类型
{
    ElemType data;
    struct LNode *next;
}LNode, *LinkList;


// 初始化一个循环单链表
bool InitList(LinkList &L)
{
    L = (LinkList)malloc(sizeof(LNode));
    if (L == NULL)
        return false;
    
    L -> next = L;      // 头结点next指向头结点
    return true;
}

// 判断循环单链表是否为空
bool Empty(LinkList L)
{
    if (L -> next == L)
        return true;
    else
        return false;
}

// 判断结点p是否为循环单链表的表尾结点
bool isTail(LinkList L, LNode *p)
{
    if (p -> next == L)
        return true;
    else
        return false;
}

  • 单链表:从一个结点出发只能找到后续的结点
  • 循环单链表:从一个结点出发可以找到其它任何一个结点

在这里插入图片描述

循环双链表

  • 双链表:表头结点的prior指向NULL;表尾结点的next指向NULL
  • 循环双链表:表头结点的prior指向表尾结点;表尾结点的next指向表头结点
    在这里插入图片描述

循环双链表的初始化

typedef struct DNode
{
    ElemType data;
    struct DNode *prior, *next;
}DNode, *DLinkList;

bool InitDLinkList(DLinkList &L)
{
    L = (LinkList)malloc(sizeof(LNode));
    if (L == NULL)
        return false;
    L -> prior = L;
    L -> next = L;
    return true;
}


bool Empty(DLinkList L)
{
    if (L -> next == L)
        return true;
    else
        return false;
}

bool isTail(LinkList L, DNode *p)
{
    if (p -> next == L)
        return true;
    else
        return false;
}

循环双链表的插入

  • 其实是双链表插入的缩减版
bool InsertNextDNode(LNode *p, LNode *s)
{
    s -> next = p -> next;
    p -> next -> prior = s;
    s -> prior = p;
    p -> next = s;
}

循环双链表的删除

  • 同上,
p -> next = q -> next;
q -> next -> prior = p;
free(q);

在这里插入图片描述

2.3.5_静态链表

  • 单链表:各个结点在内存中星罗棋布,散落天涯
  • 静态链表:分配一整片连续的空间,各个结点集中安置
  • 静态链表就是用数组的方式实现的链表
  • 优点:增、删操作不需要大量移动元素
  • 缺点:不能随机存取,只能从头结点开始依次往后查找;容量固定不可变
  • 适用场景:不支持指针的低级语言;数据元素固定不变的场景(如操作系统的文件分配表FAT)

在这里插入图片描述

用代码定义一个静态链表

#define MaxSize 10;

struct Node
{
    ElemType data;
    int next;
};

void testSLinkList()
{
    struct Node a[MaxSize];         // 数组a作为静态链表
}

在这里插入图片描述

简述基本操作的实现

  • 初始化静态链表:把a[0]的next设为-1;把其它结点的next设为一个特殊值用来表示结点空闲,如-2
  • 查找:从头结点出发挨个往后遍历结点 O(N)
  • 插入位序为i的结点:1.找到一个空的结点,存入数据元素;2.从头结点出发找到位序为i-1的结点;3.修改新结点的next;4.修改i-1号结点的next
  • 插入位序为i的结点时第一步找到一个空的结点有一个问题,那就是可能出现脏数据,所以在初始化的时候,可以让next为某个特殊的值,比如-2
  • 删除结点:1.从头结点出发找到前趋结点;2.修改前趋结点的游标;3.被删除结点next设为-2

2.3.6_顺序表vs链表

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

数据结构1800题1. 算法的计算量的大小称为计算的( )。【北京邮电大学2000 二、3 (20/8分)】 A.效率 B. 复杂性 C. 现实性 D. 难度 2. 算法的时间复杂度取决于( )【中科院计算所 1998 二、1 (2分)】 A.问题的规模 B. 待处理数据的初态 C. A和B 3.计算机算法指的是(1),它必须具备(2) 这三个特性。 (1) A.计算方法 B. 排序方法 C. 解决问题的步骤序列 D. 调度方法 (2) A.可执行性、可移植性、可扩充性 B. 可执行性、确定性、有穷性 C. 确定性、有穷性、稳定性 D. 易读性、稳定性、安全性 【南京理工大学 1999 一、1(2分) 【武汉交通科技大学 1996 一、1( 4分)】 4.一个算法应该是( )。【中山大学 1998 二、1(2分)】 A.程序 B.问题求解步骤的描述 C.要满足五个基本特性 D.A和C. 5. 下面关于算法说法错误的是( )【南京理工大学 2000 一、1(1.5分)】 A.算法最终必须由计算机程序实现 B.为解决某问题的算法同为该问题编写的程序含义是相同的 C. 算法的可行性是指指令不能有二义性 D. 以上几个都是错误的 6. 下面说法错误的是( )【南京理工大学 2000 一、2 (1.5分)】 (1)算法原地工作的含义是指不需要任何额外的辅助空间 (2)在相同的规模n下,复杂度O(n)的算法在时间上总是优于复杂度O(2n)的算法 (3)所谓时间复杂度是指最坏情况下,估算算法执行时间的一个上界 (4)同一个算法,实现语言的级别越高,执行效率就越低 A.(1) B.(1),(2) C.(1),(4) D.(3) 7.从逻辑上可以把数据结构分为( )两大类。【武汉交通科技大学 1996 一 、4(2分)】 A.动态结构、静态结构 B.顺序结构、链式结构 C.线性结构、非线性结构 D.初等结构、构造型结构
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值