系列索引2023考研王道数据结构知识梳理
第二章:线性表
线性表是具有相同数据类型的 n ( n ≥ 0 ) n(n\ge0) n(n≥0)个元素的有限序列,其中 n n n为表长,当 n = 0 n=0 n=0时,该表为空表。
命名要有可读性,最好以上述为标准,否则初始的是否要写上注释。
用顺序存储的方式实现线性表的顺序储存。把逻辑上相邻的元素存储在物理位置上也相邻的存储单元中,元素之间的关系由存储单元的邻接关系来体现。
有不明白结构体定义方式的可以看看这篇C 结构体,C与C++的结构题的区别请点击C和Cpp在结构体上的区别(不包括面向对象的区别)
#define MaxSize 10 // 定义最大的长度
typedef struct {
ElemType data[MaxSize]; // ElemType是data数组的类型,用静态的数组来存储数据。
int length; // 顺序表的当前的长度
}SqList; // 顺序表的类型定义(静态分配方式)
完整的程序
// C语言程序
#include
/* C语言中,const不是一个真真正正的常量,其代表的含义仅仅是只读。
使用const声明的对象是一个运行时对象,
无法使用其作为某个量的初值、数组的长度、case的值或在类型的情形中使用,
而在C++中可以使用。
*/
#define MaxSize 10 // 定义最大的长度
typedef struct { // 定义一个结构体
int data[MaxSize];
int length;
}SqList;
// 初始化一个顺序表,有些编译器会自动初始化为0,保险起见,我们手动初始化。
void InitList(SqList *L) { // C语言中&表示的不是引用,而是取地址符。C语言引用是“*”
for (int i = 0; i < MaxSize; i ++)
L->data[i] = 0; // 构体指针变量一般用”->”,非结构体指针变量,也就是一般结构体变量,一般用”.”
L->length = 0;
}
int main() {
SqList L; // 声明一个顺序表
InitList(&L); // 初始化顺序表
for (int i = 0; i < MaxSize; i ++) // 打印整个data数组
printf("data[%d]=%d\n", i, L.data[i]);
return 0;
}
// C++程序
#include
using namespace std;
const int MaxSize = 10; // 定义最大的长度
struct SqList{ // 定义一个结构体
int data[MaxSize];
int length;
}L; // 声明一个顺序表
void InitList(SqList &L) { // 初始化一个顺序表
for (int i = 0; i < MaxSize; i ++)
L.data[i] = 0;
L.length = 0;
}
int main() {
InitList(L); // 初始化顺序表
for (int i = 0; i < MaxSize; i ++) // 打印整个data数组
printf("data[%d]=%d\n", i, L.data[i]);
return 0;
}
#define InitSize 10 // 顺序表的初始长度
typedef struct {
ElemType *data; // 动态分配数组的指针
int MaxSize; // 顺序表的最大容量
int length; // 顺序表的当前长度
}SqList; // 顺序表的类型定义(动态分配方式)
C语言
malloc
与free
函数
两个函数的用法请点击malloc和free函数使用注意事项,C语言malloc和free使用详解
// 开辟空间
// malloc函数返回一个指针,需要强行转换为定义的ElemType数据类型的指针
L.data = (ElemType*) malloc(sizeof(ElemType) * InitSize);
// sizeof(ElemType) * InitSize是指要分配InitSize大小的一整片连续内存空间
// 释放空间
free(L.data);
C++
new
与delete
关键字
两个关键字的用法请点击c++中new和delete的使用方法
// 开辟空间
/*
格式1:指针变量名=new 类型标识符;
格式2:指针变量名=new 类型标识符(初始值);
格式3:指针变量名=new 类型标识符[内存单元个数];
*/
L.data = new ElemType[InitSize];
// 释放空间
delete [] L.data; // 不加[]释放的是第一个元素的指针
完整的程序
#include
#include // free, malloc库函数的头文件
#define InitSize 10 // 默认的最大的长度
typedef struct {
int *data; // 动态分配数组的指针
int MaxSize; // 顺序表的最大容量
int length; // 顺序表的当前长度
}SqList; // 顺序表的类型定义(动态分配方式)
void InitList(SqList *L) {
L->data = (int *)malloc(sizeof(int) * InitSize); // 用malloc函数申请一片连续的存储空间
L->MaxSize = InitSize; // 将顺序表的最大容量初始化为默认的最大的长度
L->length = 0; // 将顺序表的当前长度默认为0
}
void IncreaseSize(SqList *L, int len) { // 动态增加数组的长度
int *p = L->data; // 用指针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 += len; // 更新顺序表可以容纳的最大的长度
free(p); // 释放旧的存储空间
}
// 还可以用realloc函数
// 建议初学者使用 malloc 和 free 更能理解背后过程
void IncreaseSize(SqList *L, int len) {
/*
会先判断当前的指针所指向的内存是否有足够的连续空间,
如果有,原地扩大可分配的内存地址,并且返回原来的地址指针;
如果空间不够,先按照新指定的大小分配空间,将原有数据从头到尾拷贝到新分配的内存区域,
而后释放原来的内存区域
malloc与realloc均不会初始化
*/
L->data = (int *)realloc(L->data, sizeof(int) * 5); // 不写L->data参数和malloc一样
L->MaxSize += len;
}
int main() {
SqList L; // 声明一个顺序表
InitList(&L); // 初始化顺序表
for (int i = 0; i < L.MaxSize; i ++) // 赋值,注意要L.length要++
L.data[i] = i, L.length ++;
IncreaseSize(&L, 5); // 将顺序表扩容5个单位
for (int i = 0; i < L.MaxSize; i ++) // 打印
printf("L[%d]=%d\n", i, L.data[i]);
return 0;
}
malloc
与realloc
的区别请点击malloc与realloc的区别
// C++程序
#include
using namespace std;
const int InitSize = 10; // 默认的最大的长度
struct SqList {
int *data;
int MaxSize;
int length;
}L; // 声明一个叫L的结构体
void InitList(SqList &L) {
L.data = new int[InitSize]; // 开辟一个大小为InitSize的连续的存储空间
L.MaxSize = InitSize;
L.length = 0;
}
void IncreaseSize(SqList &L, int len) {
int *p = L.data;
// 开辟一个新的大小为InitSize + len的连续的存储空间
L.data = new int[L.MaxSize + len];
for (int i = 0; i < L.length; i ++)
L.data[i] = p[i];
L.MaxSize += len;
delete [] p; // 释放旧的存储空间
}
int main() {
InitList(L);
for (int i = 0; i < L.MaxSize; i ++)
L.data[i] = i, L.length ++;
IncreaseSize(L, 5);
for (int i = 0; i < L.MaxSize; i ++)
printf("L[%d]=%d\n", i, L.data[i]);
return 0;
}
new
和malloc
的区别请点击new和malloc有何区别?
ListInsert(&L, i, e)
在表L中的第i个位置上插入指定元素e,这里指的是位序,从1开始
#define MaxSize 10 // 定义最大长度
typedef struct {
ElemType data[MaxSize]; // 用静态数组存放数据
int length; // 顺序表的当前长度
}SqList; // 顺序表的类型定义
完整的程序
// C语言程序
#include
#include // 操作布尔类型
#define MaxSize 10
typedef struct {
int data[MaxSize];
int length;
}SqList;
// 初始化一个顺序表
void InitList(SqList *L) {
for (int i = 0; i < MaxSize; i ++)
L->data[i] = 0;
L->length = 0;
}
// 在顺序表的第i个位置插入元素e
bool ListInsert(SqList *L, int i, int e) {
// 判断i是否有效,i的有效范围[1, length + 1]
if (i < 1 || i > L->length + 1)
return false;
if (L->length >= MaxSize) // 判断是否越界
return false;
for (int j = L->length; j >= i; j --)
L->data[j] = L->data[j - 1]; // 将第i个元素及后的元素往后移动1个单位
L->data[i - 1] = e; // 在i处放上元素e
L->length ++;
return true;
}
int main() {
SqList L;
InitList(&L);
for (int i = 0; i < MaxSize - 1; i ++) // 为前9个赋值数据
L.data[i] = i, L.length ++;
if (ListInsert(&L, 10, 3)) // 在第10个位置插入数据7
puts("Insert success!!!");
else puts("Insert Fatal!!!");
for (int i = 0 ; i < MaxSize; i ++) // 打印
printf("L[%d]=%d\n", i, L.data[i]);
return 0;
}
// C++程序
#include
using namespace std;
const int MaxSize = 10;
struct SqList {
int data[MaxSize];
int length;
}L;
// 初始化一个顺序表
void InitList(SqList &L) {
for (int i = 0; i < MaxSize; i ++)
L.data[i] = 0;
L.length = 0;
}
// 在顺序表的第i个位置插入元素e
bool ListInsert(SqList &L, int i, int e) {
// 判断i是否有效,i的有效范围[1, length + 1]
if (i < 1 || i > L.length + 1)
return false;
if (L.length >= MaxSize)
return false;
for (int j = L.length; j >= i; j --) // 将第i个元素及后的元素往后移动1个单位
L.data[j] = L.data[j - 1];
L.data[i - 1] = e; // 在i处放上元素e
return true;
}
int main() {
InitList(L);
for (int i = 0; i < MaxSize - 1; i ++) // 为前9个赋值数据
L.data[i] = i, L.length ++;
if (ListInsert(L, 10, 7)) // 在第10个位置插入数据7
puts("Insert Success!!!");
else puts("Insert Fatal!!!");
for (int i = 0; i < MaxSize; i ++) // 打印
printf("L[%d]=%d\n", i, L.data[i]);
return 0;
}
并实现了数组越界后的自动扩容
// C语言程序
#include
#include
#include
#define InitSize 10
typedef struct {
int *data; // 动态分配数组的指针
int MaxSize; // 顺序表的最大容量
int length; // 顺序表的当前长度
}SqList; // 顺序表的类型定义(动态分配方式)
void InitList(SqList *L) {
L->data = (int *)malloc(sizeof(int) * InitSize); // 用malloc函数申请一片连续的存储空间
L->MaxSize = InitSize; // 将顺序表的最大容量初始化为默认的最大的长度
L->length = 0; // 将顺序表的当前长度默认为0
}
void IncreaseSize(SqList *L, int len) { // 动态增加数组的长度
int *p = L->data; // 用指针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 += len; // 更新顺序表可以容纳的最大的长度
free(p); // 释放旧的存储空间
}
bool ListInsert(SqList *L, int i, int e) {
// 判断i是否有效,i的有效范围[1, length + 1]
if (i < 1 || i > L->length + 1)
return false;
if (L->length >= L->MaxSize) // 判断是否越界
IncreaseSize(L, L->length - L->MaxSize + 1); // 越界后动态开辟空间
for (int j = L->length; j >= i; j --) // 将第i个元素及后的元素往后移动1个单位
L->data[j] = L->data[j - 1];
L->data[i - 1] = e; // 在i处放上元素e
L->length ++;
return true;
}
int main() {
SqList L;
InitList(&L);
for (int i = 0; i < L.MaxSize; i ++) // 赋值数据
L.data[i] = i, L.length ++;
if (ListInsert(&L, L.MaxSize + 1, 7)) // 在第MaxSize + 1个位置插入数据7
puts("Insert Success!!!");
else puts("Insert Fatal!!!");
for (int i = 0; i < L.MaxSize; i ++) // 打印
printf("L[%d]=%d\n", i, L.data[i]);
return 0;
}
// C++程序
#include
using namespace std;
const int InitSize = 10; // 默认的最大的长度
struct SqList {
int *data;
int MaxSize;
int length;
}L; // 声明一个叫L的结构体
void InitList(SqList &L) {
// 开辟一个大小为InitSize的连续的存储空间
L.data = new int[InitSize];
L.MaxSize = InitSize;
L.length = 0;
}
void IncreaseSize(SqList &L, int len) {
int *p = L.data;
// 开辟一个新的大小为InitSize + len的连续的存储空间
L.data = new int[L.MaxSize + len];
for (int i = 0; i < L.length; i ++)
L.data[i] = p[i];
L.MaxSize += len;
delete [] p; // 释放旧的存储空间
}
bool ListInsert(SqList &L, int i, int e) {
if (i < 1 || i > L.length + 1)
return false;
if (L.length >= L.MaxSize)
IncreaseSize(L, L.length - L.MaxSize + 1);
for (int j = L.length; j >= i; j --)
L.data[j] = L.data[j - 1];
L.data[i - 1] = e;
L.length ++;
return true;
}
int main() {
InitList(L);
for (int i = 0; i < L.MaxSize; i ++) // 赋值数据
L.data[i] = i, L.length ++;
if (ListInsert(L, L.MaxSize + 1, 7)) // 在第MaxSize + 1个位置插入数据7
puts("Insert Success!!!");
else puts("Insert Fatal!!!");
for (int i = 0; i < L.MaxSize; i ++) // 打印
printf("L[%d]=%d\n", i, L.data[i]);
return 0;
}
ListDelete(&L, i, &e)
删除表 L L L中的第 i i i个位置的元素,并用 e e e返回删除元素的值
完整程序
// C语言程序
#include
#include
#define MaxSize 10
typedef struct {
int data[MaxSize];
int length;
}SqList;
void InitList(SqList *L) {
for (int i = 0; i < MaxSize; i ++)
L->data[i] = 0;
L->length = 0;
}
bool ListDelete(SqList *L, int i, int *e) {
// 判断i是否有效,i的有效范围[1, length]
if (i < 1 || i > L->length)
return false;
*e = L->data[i - 1]; // 将被删除的元素赋值给e
for (int j = i; j < L->length; j ++)
L->data[j - 1] = L->data[j];
L->data[L->length - 1] = 0; // 将length位置的元素赋值为0;
L->length --;
return true;
}
int main() {
SqList L;
InitList(&L);
int e = -1;
for (int i = 0; i < MaxSize; i ++)
L.data[i] = i, L.length ++;
if (ListDelete(&L, 5, &e))
printf("Delete %d Success!!!\n", e);
else puts("Delete Fatal!!!");
for (int i = 0; i < MaxSize; i ++)
printf("L[%d]=%d\n", i, L.data[i]);
return 0;
}
// C++程序
#include
using namespace std;
const int MaxSize = 10;
struct SqList {
int data[MaxSize];
int length;
}L;
void InitList(SqList &L) {
for (int i = 0; i < MaxSize; i ++)
L.data[i] = 0;
L.length = 0;
}
bool ListDelete(SqList &L, int i, int &e) {
// 判断i是否有效,i的有效范围[1, length]
if (i < 1 || i > L.length)
return false;
e = L.data[i - 1]; // 将被删除的元素赋值给e
for (int j = i; j < L.length; j ++)
L.data[j - 1] = L.data[j];
L.data[L.length - 1] = 0; // 将length位置的元素赋值为0;
L.length --;
return true;
}
int main() {
InitList(L);
int e;
for (int i = 0; i < MaxSize; i ++)
L.data[i] = i, L.length ++;
if (ListDelete(L, 5, e))
printf("Delete %d Success!!!\n", e);
else puts("Delete Fatal!!!");
for (int i = 0; i < MaxSize; i ++)
printf("L[%d]=%d\n", i, L.data[i]);
return 0;
}
GetElem(L, i)
获取表 L L L中第 i i i个位置的元素的值
#define MaxSize 10
typedef struct {
ElemType data[MaxSize];
int length;
}SqList;
ElemType GetElem(SqList &L, int i) {
return L.data[i - 1];
}
完整的程序
// C语言程序
#include
#define MaxSize 10
typedef struct {
int data[MaxSize];
int length;
}SqList;
void InitList(SqList *L) {
for (int i = 0; i < MaxSize; i ++)
L->data[i] = 0;
L->length = 0;
}
int GetElem(SqList *L, int i) {
return L->data[i - 1]; // 和普通数组访问一样
}
int main() {
SqList L;
InitList(&L);
for (int i = 0; i < MaxSize;i ++)
L.data[i] = i, L.length ++;
printf("%d\n", GetElem(&L, 3));
return 0;
}
// C++程序
#include
const int MaxSize = 10;
struct SqList {
int data[MaxSize];
int length;
}L;
void InitList(SqList &L) {
for (int i = 0; i < MaxSize; i ++)
L.data[i] = 0;
L.length = 0;
}
int GetElem(SqList &L, int i) {
return L.data[i - 1]; // 和普通数组访问一样
}
int main() {
InitList(L);
for (int i = 0; i < MaxSize; i ++)
L.data[i] = i, L.length ++;
printf("%d\n", GetElem(L, 5));
return 0;
}
由于顺序表的各个数据元素在内存中连续存放,因此可以根据起始地址和数据元素大小立即找到第 i 个元素——“随机存取”特性。即时间复杂度为 O ( 1 ) O(1) O(1)。
LocateElem(L, e)
在表 L L L中查找具有给定关键字值的元素。
#define MaxSize 10
typedef struct {
ElemType data[MaxSize];
int length;
}SqList;
int LocateElem(SqList &L, ElemType e) {
for (int i = 0; i < L.length; i ++)
if (L.data[i] == e)
return i + 1;
return 0;
}
完整的程序
// C语言程序
#include
#define MaxSize 10
typedef struct {
int data[MaxSize];
int length;
}SqList;
void InitList(SqList *L) {
for (int i = 0; i < MaxSize; i ++)
L->data[i] = 0;
L->length = 0;
}
int LocateElem(SqList *L, int e) {
for (int i = 0; i < L->length; i ++)
if (L->data[i] == e)
return i + 1; // 数组下标为i的元素值等于e,返回其位序i+1
return 0; // 查询失败返回0
}
int main() {
SqList L;
InitList(&L);
for (int i = 0; i < MaxSize; i ++)
L.data[i] = i, L.length ++;
printf("%d\n", LocateElem(&L, 5));
return 0;
}
// C++程序
#include
using namespace std;
const int MaxSize = 10;
struct SqList {
int data[MaxSize];
int length;
}L;
void InitList(SqList &L) {
for (int i = 0; i < MaxSize; i ++)
L.data[i] = 0;
L.length = 0;
}
int LocateElem(SqList &L, int e) {
for (int i = 0; i < L.length; i ++)
if (L.data[i] == e)
return i + 1; // 数组下标为i的元素值等于e,返回其位序i+1
return 0; // 查询失败返回0
}
int main() {
InitList(L);
for (int i = 0; i < MaxSize; i ++)
L.data[i] = i, L.length ++;
printf("%d\n", LocateElem(L, 5));
return 0;
}
定义:链式存储︰用一组任意的存储单元存储线性表中的数据元素。用这种方法存储的线性表简称线性链表。每一个结点只包含一个指针域的链表称为单链表
结点的组成
数据域:存放数据元素
指针域:存放后继结点的地址
首元结点:链表中存储第一个数据元素 a 1 a_1 a1的结点
头指针:指向链表中第一个结点的指针,通常用用来标识一个单链表
头结点:在链表的首元结点之前附加的一个结点,指针域指向线性表的首元结点,头结点的数据域可以不设任何信息,也可以只放空表标志和表长等信息,但此结点不能计入链表长度值
头指针与头结点的区分:不管是否带有头结点,头指针均指向链表的首元结点,而头结点是带头结点的链表中的首元结点,结点内通常不存储信息
不带头结点VS带头结点
单链表:结点只有一个指针域的链表,称为单链表或线性链表
双链表:有两个指针域的链表,称为双链表
循环链表:首尾相接的链表称为循环链表
typedef struct { // 定义单链表结点类型
ElemType val; // 每个结点存放一个数据元素,数据域
struct LNode *next; // 指针指向下一个结点,指针域
}LNode, *LinkList;
// LNode *强调的是一个结点,LinkList强调的是一个链表
先入后出,先插入的在后面
核心代码
// C语言程序
// 头插法,逆向建立单链表
ListNode *ListHeadInsert(LinkList head) {
// 头插法读入与输出相反
for (int i = 1; i < 10; i ++) { // 生成一个链表
ListNode *p = (ListNode *)malloc(sizeof(ListNode));
/*
头插法与尾插法的不同之处主要在此
新结点p的指针域指向头指针指向的区域
*/
p->next = head;
p->val = i;
// 令头指针指向新结点
head = p;
}
return head;
}
// C++代码
// 头插法,逆向建立单链表
ListNode *ListHeadInsert(ListNode *head) {
// 头插法读入与输出相反
for (int i = 1; i < 10; i ++) { // 生成一个链表
ListNode *p = new ListNode(i);
/*
头插法与尾插法的不同之处主要在此
新结点p的指针域指向头指针指向的区域
*/
p->next = head;
// 另头指针指向新结点
head = p;
}
return head;
}
核心代码
// C语言代码
// 头插法,逆向建立单链表
ListNode *ListHeadInsert(LinkList head) {
// 头插法读入与输出相反
for (int i = 1; i < 10; i ++) { // 生成一个链表
ListNode *p = (ListNode *)malloc(sizeof(ListNode));
/*
头插法与尾插法的不同之处主要在此
新结点p的指针域指向头指针指向的区域
*/
p->next = head->next;
p->val = i;
// 令头指针指向新结点
head->next = p;
}
return head;
}
// C++代码
// 头插法,逆向建立单链表
ListNode *ListHeadInsert(ListNode *head) {
// 头插法读入与输出相反
for (int i = 1; i < 10; i ++) { // 生成一个链表
ListNode *p = new ListNode(i);
/*
头插法与尾插法的不同之处主要在此
新结点p的指针域指向头指针指向的区域
*/
p->next = head->next;
// 另头指针指向新结点
head->next = p;
}
return head;
}
核心代码
// C语言程序
// 尾插法,正向建立单链表
ListNode *ListTailInsert(LinkList head) {
ListNode *r = head;
for (int i = 1; i < 10; i ++) { // 生成一个链表
ListNode *p = (ListNode *)malloc(sizeof(ListNode));
/*
头插法与尾插法的不同之处主要在此
*/
p->val = i;
if (!head) { // 特判下head初始化后指向NULL
head = p;
r = p;
continue;
}
// 令尾指针的next指针指向新结点
r->next = p;
r = p; // 尾指针指向新结点
}
r->next = NULL; // 将尾指针置空
return head;
}
// C++代码
// 尾插法,正向建立单链表
ListNode *ListHeadInsert(ListNode *head) {
ListNode *r = head;
for (int i = 1; i < 10; i ++) { // 生成一个链表
ListNode *p = new ListNode(i);
/*
头插法与尾插法的不同之处主要在此
*/
if (!head) {
head = p;
r = p;
continue;
}
r->next = p;
r = p;
}
r->next = NULL;
return head;
}
核心代码
// C语言代码
// 尾插法,正向建立单链表
ListNode *ListHeadInsert(LinkList head) {
LinkList r = head;
for (int i = 1; i < 10; i ++) { // 生成一个链表
ListNode *p = (ListNode *)malloc(sizeof(ListNode));
/*
头插法与尾插法的不同之处主要在此
*/
p->val = i;
r->next = p;
r = p;
}
r->next = NULL;
return head;
}
// C++代码
// 尾插法,正向建立单链表
ListNode *ListTailInsert(ListNode *head) {
ListNode *r = head;
for (int i = 1; i < 10; i ++) { // 生成一个链表
ListNode *p = new ListNode(i);
/*
头插法与尾插法的不同之处主要在此
*/
r->next = p;
r = p;
}
r->next = NULL;
return head;
}
GetElem(L, i)
:按位查找,获取表 L L L中第 i i i个位置的元素的值
核心代码
// C语言程序
ListNode *GetElem(LinkList head, int i) {
if (i < 0)
return NULL;
ListNode *p; // 指针p指向当前扫描到的结点
int j = 0; // 当前指针p指向的第几个结点
p = head; // 头结点是第0个结点不存储数据
while (p && j < i) // 循环找到第i个结点
p = p->next, j ++;
return p;
}
// C++程序
ListNode *GetElem(ListNode *head, int i) {
if (i < 0)
return NULL;
ListNode *p; // 指针p指向当前扫描到的结点
int j = 0; // 当前指针p指向的第几个结点
p = head; // 头结点是第0个结点不存储数据
while (p && j < i) // 循环找到第i个结点
p = p->next, j ++;
return p;
}
核心代码
// C语言程序
ListNode *LocateElem(LinkList head, int e) {
ListNode *p = head->next;
while (p && p->data != e)
p = p->next;
return p;
}
// C++程序
ListNode *LocateElem(ListNode *head, int e) {
ListNode *p = head->next;
while (p && p->val != e)
p = p->next;
return p;
}
InsertPriorNode(p e)
:在指定结点 p p p之前插入元素 e e e
核心代码
// C语言程序
bool InsertPriorNode(ListNode *p, int e) {
if (!p)
return false;
ListNode *q = (ListNode *)malloc(sizeof(ListNode));
if (!q)
return false;
q->next = p->next;
p->next = q;
q->val = p->val; // 将指定结点p中的val赋值给新结点的val
p->val = e; // 将新结点的val赋值e
return true;
}
// C++程序
bool InsertPriorNode(ListNode *p, int e) {
if (!p)
return false;
ListNode *q = new ListNode(e);
if (!q)
return false;
q->next = p->next;
p->next = q;
q->val = p->val; // 将指定结点p中的val赋值给新结点的val
p->val = e; // 将新结点的val赋值e
return true;
}
InsertNextNode(p, e)
:在结点 p p p后面插入指定元素 e e e
核心代码
// C语言程序
bool InsertNextNode(ListNode *p, int e) {
if (!p)
return false;
ListNode *q = (ListNode *)malloc(sizeof(ListNode));
if (!q)
return false;
q->val = e; // 用结点q保存数据e
q->next = p->next;
p->next = q;
return true;
}
// C++程序
bool InsertNextNode(ListNode *p, int e) {
if (!p)
return false;
ListNode *q = new ListNode(e);
if (!q)
return false;
q->next = p->next;
p->next = q;
return true;
}
ListInsert(&L, i, e)
:在表 L L L的第 i i i个位置插入指定元素e
不存在 “第0个”结点,因此 i=1 时需要特殊处理
核心代码
// C语言程序
// 按位序插入(不带头结点)
bool ListInsert(LinkList *head, int i, int e) {
if (i < 1)
return false;
if (i == 1) { // 插入第1个结点的操作与其他结点不同,需要特殊处理
ListNode *p = (ListNode *)malloc(sizeof(ListNode));
p->val = e;
p->next = *head;
*head = p; // 头指针指向新结点
return true;
}
ListNode *p; // 指针指向当前扫描到的结点
int j = 1; // 当前p指针指向的第几个结点
p = *head; // p指向第1个结点(首元结点,不是头结点)
while (p && j < i - 1)
p = p->next, j ++;
InsertNextNode(p, e);
}
// C++代码
// 按位序插入(不带头结点)
bool ListInsert(ListNode *&head, int i, int e) {
if (i < 1)
return false;
if (i == 1) {
ListNode *p = new ListNode(e);
p->next = head;
head = p;
return true;
}
ListNode *p; // 指针指向扫描到的结点
int j = 1; // 当前指针p指向的是第几个结点
p = head; // p指向第一个结点(首元结点)不是头结点
while (p && j < i - 1)
p = p->next, j ++;
InsertNextNode(p, e);
}
头结点可以看作是“第0个”结点
核心代码
// C语言代码
// 按位序插入(带头结点)
bool ListInsert(LinkList head, int i, int e) {
if (i < 1)
return false;
ListNode *p = GetElem(head, i - 1);
InsertNextNode(p, e);
}
// C++代码
// 按位序插入(带头结点)
bool ListInsert(ListNode *head, int i, int e) {
if (i < 1)
return false;
ListNode *p = GetElem(head, i - 1);
InsertNextNode(p, e);
}
ListDelete(&L, i, &e)
:删除操作,删除表 L L L中第 i i i个位置的元素,并用 e e e返回删除元素的值
核心代码
// C语言程序
bool ListDelete(LinkList head, int i, int *e) {
if (i < 1)
return false;
ListNode *p = GetElem(head, i - 1); // 找到第i个结点
if (!p || !p->next)
return false;
ListNode *q = p->next; // 令指针q指向被删除的结点
e = q->val; // 用e返回被删除的值
p->next = q->next; // 将被删除的结点从链中断开
free(q) // 释放被删除的结点
return true;
}
// C++程序
bool ListDelete(ListNode *head, int i, int &e) {
if (i < 1)
return false;
ListNode *p = GetElem(head, i - 1); // 找到第i个结点
if (!p || !p->next)
return false;
ListNode *q = p->next; // 令指针q指向被删除的结点
p->next = q->next; // 将被删除的结点从链中断开
delete q; // 释放被删除的结点
return true;
}
DeleteNode(&p)
:删除指定结点p
方法一:传入链表头指针,循环遍历找到该结点的前一个结点
方法二:偷天换日,交换val,但是当要删除最后一个结点的时候只能使用方法一
核心代码
// C语言程序
bool DeleteNode(ListNode *p) {
if (!p)
return false;
ListNode *q = p->next; // 令q指针指向p的后继结点
p->val = q->val; // 和后继结点交换数据
p->next = q->next; // 将结点q从链表中断开
free(q); // 释放结点q
return true;
}
// C++程序
bool DeleteNode(ListNode *p) {
if (!p)
return false;
ListNode *q = p->next;
p->val = q->val;
p->next = q->next;
delete q;
return true;
}
核心代码
// C语言程序
int Length(LinkList head) {
int len = 0;
ListNode *p = head;
while (p->next)
p = p->next, len ++;
return len;
}
// C++程序
int Length(ListNode *head) {
int len = 0;
ListNode *p = head;
while (p->next)
p = p->next, len ++;
return len;
}
单链表的完整程序单链表基本操作的完整程序
只介绍初始化、插入、删除、销毁。按值查找,和按位查找均与单链表相同
typedef struct DListNode {
ElemType val;
struct DListNode *prior, *next;
}DListNode, *DLinkList;
核心代码
// C语言程序
bool DInitList(DLinkList *head) {
DListNode *p = (DListNode *)malloc(sizeof(DListNode));
if (!p)
return false;
p->prior = NULL; // 头结点的prior永远指向NULL
p->next = NULL;
*head = p;
return true;
}
// C++程序
bool DInitList(DListNode *&head) {
DListNode *p = new DListNode();
if (!p)
return false;
p->prior = NULL; // 头结点的prior永远指向NULL
p->next = NULL;
head = p;
return true;
}
核心代码
// C语言程序和C++程序相同
// 在p结点之后插入s结点
bool InsertDListNode(DListNode *p, DListNode *s) {
if (!p || !s)
return false;
s->next = p->next;
if (!p->next)
p->next->prior = s;
s->prior = p;
p->next = s;
return true;
}
核心代码
// C语言程序和C++程序相同
// 删除p的后继结点
bool DeleteNextDListNode(DListNode *p) {
if (!p || !p->next)
return false;
DListNode *q = p->next;
p->next = q->next;
if (!q->next)
q->next->prior = p;
free(q);
// delete q // C++可以写这个
return true;
}
核心代码
// C语言程序
// 销毁双链表
void DestoryList(DLinkList *head) {
// 循环释放每个结点
while ((*head)->next)
DeleteNextDListNode(*head);
free(*head); // 释放头结点
*head = NULL; // 头指针指向NULL
}
// C++程序
// 销毁双链表
void DestoryList(DListNode *&head) {
// 循环释放每个结点
while (head->next)
DeleteNextDListNode(head);
delete head; // 释放头结点
head = NULL; // 头指针指向NULL
}
核心代码
// C语言程序
// 初始化一个循环单链表
bool InitList(LinkList *head) {
ListNode *p = (ListNode *)malloc(sizeof(ListNode));
if (!p)
return false;
head = p;
head->next = head; // 头结点next的指针指向头结点
return true;
}
// C++程序
bool InitList(ListNode *&head) {
ListNode *p = new ListNode();
if (!p)
return false;
head = p;
head = p;
head->next = head;
return false;
}
核心代码
// C语言程序
bool InitDLinkList(DLinkList *head) {
ListNode *p = (ListNode *)malloc(sizeof(ListNode));
if (!p)
return false;
head = p;
head->prior = head;
head->next = head;
}
// C++程序
bool InitDLinkList(DListNode *&head) {
ListNode *p = new ListNode();
if (!p)
return false;
head = p;
head->prior = head;
head->next = head;
return true;
}
核心代码
// C语言程序
bool InsertNextDListNode(DListNode *p, DListNode *s) {
s->next = p->next;
p->next->prior = s;
s->prior = p;
p->next = s;
}
// C++程序
顺序表可以顺序存取,也可以随机存取,链表只能从表头顺序存取元素。例如在第 i i i个位置上执行存或取的操作,顺序表仅需一次访问,而链表则需从表头开始依次访问 i i i次。
采用顺序存储时,逻辑上相邻的元素,对应的物理存储位置也相邻。而采用链式存储时,逻辑上相邻的元素,物理存储位置不一定相邻,对应的逻辑关系是通过指针链接来表示的。
对于按值查找,顺序表无序时,两者的时间复杂度均为 O ( n ) O(n) O(n);顺序表有序时,可采用折半查找,此时的时间复杂度为 O ( l o g n ) O(logn) O(logn)
对于按序号查找,顺序表支持随机访问,时间复杂度仅为 O ( 1 ) O(1) O(1),而链表的平均时间复杂度为 O ( n ) O(n) O(n)。顺序表的插入、删除操作,平均需要移动半个表长的元素。链表的插入、删除操作,只需修改相关结点的指针域即可。由于链表的每个结点都带有指针域,故而存储密度不够大。
顺序存储在静态存储分配情形下,一旦存储空间装满就不能扩充,若再加入新元素,则会出现内存溢出,因此需要预先分配足够大的存储空间。预先分配过大,可能会导致顺序表后部大量闲置;预先分配过小,又会造成溢出。动态存储分配虽然存储空间可以扩充,但需要移动大量元素,导致操作效率降低,而且若内存中没有更大块的连续存储空间,则会导致分配失败。链式存储的结点空间只在需要时申请分配,只要内存有空间就可以分配,操作灵活、高效
如何选取存储结构
基于存储方面:长度或规模难以估计,不宜选择顺序表,链表不用事先估计存储规模,但是链表的存储密度比较低,显然链式存储结构的存储密度是小于1的
基于运算的考虑:顺序表是开辟一大片连续空间存储,可以随机存取,按序号访问的时间复杂度为 O ( 1 ) O(1) O(1)。而链表在物理逻辑上不相邻,所以访问元素的时间复杂度为 O ( n ) O(n) O(n),因此,经常做的运算是按序号访问数据元素,显然顺序表优于链表。在顺序表中插入删除操作的时候,平均移动一半的元素,当数据元素量较大的时候,速度会变得很慢,在链表中插入,删除的时候,虽然也要找插入的位置,但是主要的是比较操作,显然链表优于顺序表
顺序表容易实现,任何高级语言都有数组类型,而链表的操作是基于指针,显然顺序表实现较链表简单
注:如果您通过阅读本文解决了问题,恳请您留下一个宝贵的赞再离开,如果不嫌弃的话,点个关注再走吧,非常欢迎您能够在讨论区指出此文的不足处,博主会及时对文章加以修正 !如果有任何问题,欢迎评论,非常乐意为您解答!( •̀ ω •́ )✧