王道考研之数据结构顺序表和链表

数据结构系列

提示:这里可以添加系列文章的所有文章的目录,目录需要自己手动添加



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
指定节点是最后一个节点时需要单独处理:
在这里插入图片描述

4.

4.1

4.2

4.3

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值