数据结构系列
提示:这里可以添加系列文章的所有文章的目录,目录需要自己手动添加
数据结构
1.线性表
1.1 线性表的定义和相关概念
线性表:具有相同数据的序列。线性的表
包含顺序表(数组)和链表。
概念 | 描述 |
---|---|
位序 | 从1开始计数,用 i 表示位序。 |
数组下标 | 从0开始计数,用 index 表示数组下标,其中 index + 1 = i |
表头元素 | 线性表的第一个元素。 |
表尾元素 | 线性表的最后一个元素。 |
前驱 | 前一个元素,即当前元素的前一个位置的元素。 |
后驱 | 后一个元素,即当前元素的后一个位置的元素。 |
1.2 线性表的创销 增删查改 判空表长打印
2.顺序表
2.1 顺序表定义和相关概念
顺序表:逻辑上相邻的元素,物理上也相邻。----数组结构
2.2 顺序表的静态实现
缺点是:定义后无法扩容
#define capacity 10
typedef int myDataType
typedef struct
{ myDataType data[capacity];
int size;//顺序表当前的数据长度
}SqList;
2.3 顺序表的动态实现
#define capacity 10
typedef int myDataType
typedef struct
{ myDataType *data;
int size;//顺序表当前的数据长度
int capacity;//顺序表的容量
}SqList;
2.4 顺序表的指定位置插入和指定位置删除
2.4.1 顺序表的指定位置插入
在index位置插入数据,index(取代index位置,因此index也要挪动)和index之后的数据都需要挪动
则挪动的数据的数据下标范围是[index,size-1]
如何将index位置数据挪动呢?
向后挪,为了放在覆盖,则需要从最后开始向后挪动。
#include <assert.h> // 包含assert.h以使用assert
typedef struct {
int *data; // 动态分配的数组
int size; // 顺序表的当前长度
} SqList;
// 插入元素
void ListInsert(SqList *L, int index, int e) {
// 确保index在合法范围内
assert(index >= 0 && index <= L->size);
// 检查是否有足够的空间插入新元素
if (L->size == L->capacity) {
// 这里需要实现扩容逻辑,例如:
int newCapacity = L->capacity * 2;
int *newData = (int *)realloc(L->data, newCapacity * sizeof(int));
if (!newData) {
exit(EXIT_FAILURE); // 内存分配失败,退出程序
}
L->data = newData;
L->capacity = newCapacity;
}
// 向后挪动
for (int p = L->size - 1; p >= index; p--) {
L->data[p + 1] = L->data[p];
//关于 L->data[p + 1] = L->data[p];
//和 L->data[p] = L->data[p-1];
}
// 插入新元素
L->data[index] = e;
L->size++;
}
2.4.2 顺序表的指定位置删除
在index位置删除数据,后面的数据都需要向前挪动,为了防止覆盖,需要从最前面的位置开始挪动。
则挪动的数据的数据下标范围是[index+1,size-1]
如何将index位置数据向前挪,呢?
p到p-1 不就是前挪吗?
#include <assert.h> // 包含assert.h以使用assert
typedef struct {
int *data; // 动态分配的数组
int size; // 顺序表的当前长度
} SqList;
// 插入元素
void Listdelete(SqList *L, int index) {
// 确保index在合法范围内
assert(index >= 0 && index <= L->size);
for (int p = index+1; p <=size-1 ; p++) {
L->data[p -1] = L->data[p];
}
L->size--;
}
2.4.3 顺序表的指定位置插入和删除时间复杂度
操作 | 最好情况时间复杂度 | 最坏情况时间复杂度 | 平均情况时间复杂度 | 说明 |
---|---|---|---|---|
插入 | O(1) | O(n) | O(n) | 最坏情况是在表头插入,需要移动所有元素;平均情况是插入位置在中间,需要移动一半的元素。 |
删除 | O(1) | O(n) | O(n) | 最好情况是在表尾删除,不需要移动元素;最坏情况是在表头删除,需要移动所有元素;平均情况是删除位置在中间,需要移动一半的元素。 |
清空 | O(1) | O(n) | O(n) | 最好情况是直接释放整个表的内存;最坏情况是逐个元素删除;平均情况取决于实现方式,通常为 O(n)。 |
3 单链表
3.1 单链表的概念
一个数据,一个指针,指针指向下一个节点
3.2 单链表结构体定义
typedef struct LNode
{
int data;
struct LNode* next;
}LNode,*LinkList;//LNode为结构体类型的重命名,LNode为结构体指针类型的重命
>>>>>>>>>>>>>>>等价于下面>>>>>>>>>>>>>>>>>>>>>
struct LNode
{
int data;
struct LNode* next;
};
typedef struct LNode LNode;
typedef struct LNode* LinkList;
关于结构体指针:LNode和LinkList,其实都是结构体指针,但是侧重点不同
LNode 强调节点
LinkList 强调整个链表,也就是头节点。phead
3.3 带头节点的单链表和不带头节点的单链表区别
区别:
由于不带头节点的链表,改变头节点指向时需要单独处理,代码写起来更麻烦
带头节点的链表,由于已经固定一个头节点,无法改变头结点指向,代码写起来更简单。
why?改变头节点指向时需要单独处理
3.4 指定位置插入节点
不带头结点的插入
index从0开始
bool ListInsert(LinkList* pphead,int index, int e)
{
if(index<0)//不合法index
return false;
if(index = 0) //头插
{
LNode *s = (LNode *)malloc(sizeof(LNode));
s->next = *pphead;
(*pphead) = s;
return true;
}
if (index > 0)//不是头插
{
LNode *pos = *pphead;
for (i = 0; i < index && pos!=NULL; i++)
{
pos = pos->next;
}
if ((*pos) == NULL)
return false;
LNode *s = (LNode *)malloc(sizeof(LNode));
s->data = e;
s->next = pos->next;
pos->next = s;
return true;
}
}
带头结点的插入
头index=0
第一个元素index=1
bool ListInsert(LinkList* pphead,int index, int e)
{
if(index<=0)//不合法index
return false;
if (index > 0)
{
LNode *pos = *pphead;
for (i = 0; i < index && pos!=NULL; i++)
{
pos = pos->next;
}
if ((*pos) == NULL)
return false;
LNode *s = (LNode *)malloc(sizeof(LNode));
s->data = e;
s->next = pos->next;
pos->next = s;
return true;
}
}
3.4 指定节点指针的(带头)后插和(带头)前插
带头后插 由于找后简单,但是无法找前
bool InsertNextNode(LNode* p , int e)
{
if(p=NULL)
return false;
s->data = e;
s->next = p;
p->next = s;
return true;
}
(带头)前插
由于已知P节点地址,找后简单,但是无法找前,所以前插有二种方式
方法1.知道头节点,从前往后找,指定找到P节点和P的前一个节点
方法2.直接在P后面插入一个节点,如何把P和后面插入的节点交换
方法1:
bool InsertPriorNode(Linklist phead, LNode *p, int e) {
if (phead == NULL || p == NULL) {
return false; // 如果头节点或p节点为空,返回失败
}
LNode *current = phead;
while (current->next != NULL && current->next != p) {
current = current->next;
}
if (current->next == NULL || current->next != p) {
return false; // 如果current为NULL或者current->next不等于p,说明p不在链表中,返回失败
}
LNode *s = (LNode *)malloc(sizeof(LNode));
if (s == NULL) {
return false; // 如果内存分配失败,返回失败
}
s->data = e; // 设置新节点的数据域
s->next = p; // 新节点指向p节点
current->next = s; // 将current节点的next指向新节点
return true; // 插入成功,返回true
}
方法2:
bool InsertPriorNode(LNode*p,int e)
{
if(p==NULL)
return false;//p节点为空,返回失败
LNode *s = (LNode *)malloc(sizeof(LNode));
s->next = p->next;
p->next = s;
s->data = p->data; //s空着,放入P的数据
p->data = e; //在把p中放入e的数据
return true;
}
3.5 指定节点指针的删除(带头)
(带头)指定节点的删除
由于已知P节点地址,删除节点需要找前。找后简单,但是无法找前,
所以删除有二种方式
方法1.知道 头节点,从前往后找,指定找到P节点和P的前一个节点
方法2.把P和后面插入的节点数据交换,前后数据交换,删后面一个数据即可。
方法二:
注意事项:一前一后指针,要注意范围
不仅要考虑前面指针范围,也要考虑后面指针范围。
当P指向最后一个元素时,P的下一个指针指向是NULL
指定节点是最后一个节点时需要单独处理: