【数据结构】顺序表和链表详解顺序表和链表的实现

主页:醋溜马桶圈-CSDN博客

专栏:数据结构_醋溜马桶圈的博客-CSDN博客

gitee:mnxcc (mnxcc) - Gitee.com

目录

1.线性表

1.1 顺序表

1.1.1 概念及结构

1.1.2 静态顺序表

1.1.3 动态顺序表

1.2 链表

1.2.1 链表的概念及结构

1.2.2 链表的分类 

1.2.2.1 单向或者双向

1.2.2.2 带头或者不带头

1.2.2.3 循环或者非循环

1.3 顺序表和链表的区别

2.顺序表的实现

2.1 创建顺序表

2.2 基本的增删查改接口

2.2.1 顺序表初始化

2.2.2 顺序表的销毁

2.2.3 检查顺序表的容量

2.2.4 打印顺序表 

2.2.5 顺序表的尾插

2.2.6 顺序表的头插

2.2.7 顺序表的尾删

2.2.8 顺序表的头删

2.2.9 任意位置的插入

2.2.10 任意位置的删除

2.2.11 顺序表的查找

2.3实现代码

2.3.1 SeqList.h

2.3.2 SeqList.c

3.单链表的实现

3.1 认识单链表

3.2 创建单链表

3.3 单链表的操作

3.3.1 打印单链表

3.3.2 开辟新空间

3.3.3 尾插

3.3.4 头插

3.3.5 尾删

3.3.6 头删

3.3.7 查找

3.3.8 在pos前面插入

3.3.9 删除pos位置

3.3.10 销毁

3.3.11 在pos位置之后插入

3.3.12 删除pos位置之后的值

3.4 实现代码

3.4.1 SList.h

3.4.2 SList.c

4.带头双向循环链表的实现

4.1 认识带头双向循环链表

4.1.1 双向链表

4.1.2 循环链表

4.1.3 带头链表

4.1.4 带头双向循环链表

4.1.5 双向链表的优势和不足

4.1.6 顺序表的优势和不足

4.2 实现带头双向循环链表

4.2.1 创建带头双向循环链表

4.2.2 初始化

4.2.3 创建返回链表的头结点

4.2.4 打印链表

4.2.5 尾插

4.2.6 尾删

4.2.7 头插

4.2.8 头删

4.2.9 查找

4.2.10 在pos位置前插入

4.2.11 删除pos位置

4.2.12 销毁

4.3 实现代码

4.3.1 ListNode.h

4.3.2 ListNode.c

5.数组下标为什么从0开始

5.1 原因

5.2 拓展

6.环型链表

6.1 环型链表是什么

6.2 快慢指针判断环形链表

6.2.1 思考

6.2.2 推导思路

6.2.3 结论


1.线性表

线性表(linear list)是n个具有相同特性的数据元素的有限序列

线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串..

线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储:

1.1 顺序表

1.1.1 概念及结构

顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构

一般情况下采用数组存储,在数组上完成数据的增删查改

顺序表一般可以分为:

1.1.2 静态顺序表

静态顺序表:使用定长数组存储元素



1.1.3 动态顺序表

动态顺序表:使用动态开辟的数组存储

1.2 链表

1.2.1 链表的概念及结构

概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的

现实中

数据结构中

注意:

  1. 从上图可以看出,链式结构在逻辑上是连续的,但在物理上不一定连续
  2. 现实中的结点一般都是从堆上申请出来的 
  3. 从堆上申请的空间,是按照一定的策略来分配的,两次申请的空间可能连续,也可能不连续

1.2.2 链表的分类 

实际中链表的结构非常多样,以下情况组合起来就有8种链表结构:

1.2.2.1 单向或者双向

1.2.2.2 带头或者不带头

1.2.2.3 循环或者非循环

虽然有这么多的链表的结构,但是我们实际中最常用还是两种结构: 

无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。

带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向 循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了,后面我们代码实现了就知道了。

1.3 顺序表和链表的区别

与程序员相关的CPU缓存知识 | 酷 壳 - CoolShell

2.顺序表的实现

2.1 创建顺序表

2.2 基本的增删查改接口

2.2.1 顺序表初始化

顺序表的初始化我们只需要讲指针置为空指针
然后将当前数据元素个数和最大数据元素个数置为0
到插入时我们便会动态开辟空间给指针a

//顺序表的初始化
void SLInit(SL* ps)
{ps->a = NULL;//置为空指针ps->size = 0;//数据个数为0ps->capacity = 0;//空间大小置为0
}

2.2.2 顺序表的销毁

//顺序表的销毁
void SLDestroy(SL* ps)
{if (ps->a != NULL){free(ps->a);ps->a = NULL;ps->size = 0;ps->capacity = 0;}
}

2.2.3 检查顺序表的容量

//检查顺序表的容量
void SLCheckCapacity(SL* ps)
{if (ps->size == ps->capacity){int newCapacity = ps->capacity = 0 ? 4 : ps->capacity * 2;SLDataType* tmp = realloc(ps->a, sizeof(SLDataType) * newCapacity);if (tmp == NULL){perror("realloc fail");return;}ps->a = tmp;ps->capacity = newCapacity;}
}

2.2.4 打印顺序表 

//打印顺序表
void SLPrint(SL* ps)
{for (int i = 0; i < ps->size; i++){printf("%d ", ps->a[i]);}printf("\n");
}

2.2.5 顺序表的尾插

//顺序表的尾插
void SLPushBack(SL* ps, SLDataType x)
{SLCheckCapacity(ps);ps->a[ps->size] = x;ps->size++;
}

2.2.6 顺序表的头插

//顺序表的头插
void SLPushFront(SL* ps, SLDataType x)
{SLCheckCapacity(ps);int end = ps->size - 1;while (end >= 0){ps->a[end + 1] = ps->a[end];}ps->a[0] = x;ps->size++;
}

2.2.7 顺序表的尾删

//顺序表的尾删
void SLPopBack(SL* ps)
{assert(ps->size > 0);//ps->a[ps->size - 1] = -1;ps->size--;
}

2.2.8 顺序表的头删

//顺序表的头删
void SLPopFront(SL* ps)
{//for (int i = 0; i < ps->size; i++)//{//	ps->a[i] = ps->a[i + 1];//}//ps->size--;assert(ps);assert(ps->size > 0);int begin = 1;while (begin < ps->size){ps->a[begin - 1] = ps->a[begin];++begin;}ps->size--;
}

2.2.9 任意位置的插入

//任意位置的插入
void SLInsert(SL* ps, int pos, SLDataType x)
{assert(ps);assert(pos >= 0 && pos <= ps->size);SLCheckCapacity(ps);int end = ps->size - 1;while (end >= pos){ps->a[end + 1] = ps->a[end];--end;}ps->a[pos] = x;ps->size++;
}

2.2.10 任意位置的删除

//任意位置的删除
void SLErase(SL* ps, int pos)
{assert(ps);assert(pos >= 0 && pos <= ps->size);int begin = pos;while (begin < ps->size){ps->a[begin] = ps->a[begin+1];++begin;}ps->size--;
}

2.2.11 顺序表的查找

//顺序表的查找
//找到返回下标,找不到返回-1
int SLFind(SL* ps, SLDataType x)
{assert(ps);for (int i = 0; i < ps->size; i++){if (ps->a[i] == x){return i;}}return -1;
}

2.3实现代码

2.3.1 SeqList.h

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef int SLDataType;
//顺序表的动态存储
typedef struct SeqList
{SLDataType* a;     //指向动态开辟的数组int size;           //有效元素个数int capacity;       //容量空间的大小
}SL;//顺序表的初始化
void SLInit(SL* ps);
//顺序表的销毁
void SLDestroy(SL* ps);//检查顺序表的容量
void SLCheckCapacity(SL* ps);
//打印顺序表
void SLPrint(SL* ps);//顺序表的尾插
void SLPushBack(SL* ps, SLDataType x);
//顺序表的头插
void SLPushFront(SL* ps, SLDataType x);
//顺序表的尾删
void SLPopBack(SL* ps);
//顺序表的头删  
void SLPopFront(SL* ps);//任意位置的插入
void SLInsert(SL* ps, int pos, SLDataType x);
//任意位置的删除
void SLErase(SL* ps, int pos);//顺序表的查找
//找到返回下标,找不到返回-1
int SLFind(SL* ps, SLDataType x);

2.3.2 SeqList.c

#define _CRT_SECURE_NO_WARNINGS 1
#include "SeqList.h"//顺序表的初始化
void SLInit(SL* ps)
{ps->a = NULL;//置为空指针ps->size = 0;//数据个数为0ps->capacity = 0;//空间大小置为0
}
//顺序表的销毁
void SLDestroy(SL* ps)
{if (ps->a != NULL){free(ps->a);ps->a = NULL;ps->size = 0;ps->capacity = 0;}
}
//检查顺序表的容量
void SLCheckCapacity(SL* ps)
{if (ps->size == ps->capacity){int newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;SLDataType* tmp = realloc(ps->a, sizeof(SLDataType) * newCapacity);if (tmp == NULL){perror("realloc fail");return;}ps->a = tmp;ps->capacity = newCapacity;}
}
//打印顺序表
void SLPrint(SL* ps)
{for (int i = 0; i < ps->size; i++){printf("%d ", ps->a[i]);}printf("\n");
}
//顺序表的尾插
void SLPushBack(SL* ps, SLDataType x)
{SLCheckCapacity(ps);ps->a[ps->size] = x;ps->size++;
}
//顺序表的头插
void SLPushFront(SL* ps, SLDataType x)
{SLCheckCapacity(ps);int end = ps->size - 1;while (end >= 0){ps->a[end + 1] = ps->a[end];--end;}ps->a[0] = x;ps->size++;
}
//顺序表的尾删
void SLPopBack(SL* ps)
{assert(ps->size > 0);//ps->a[ps->size - 1] = -1;ps->size--;
}
//顺序表的头删
void SLPopFront(SL* ps)
{//for (int i = 0; i < ps->size; i++)//{//	ps->a[i] = ps->a[i + 1];//}//ps->size--;assert(ps);assert(ps->size > 0);int begin = 1;while (begin < ps->size){ps->a[begin - 1] = ps->a[begin];++begin;}ps->size--;
}
//任意位置的插入
void SLInsert(SL* ps, int pos, SLDataType x)
{assert(ps);assert(pos >= 0 && pos <= ps->size);SLCheckCapacity(ps);int end = ps->size - 1;while (end >= pos){ps->a[end + 1] = ps->a[end];--end;}ps->a[pos] = x;ps->size++;
}
//任意位置的删除
void SLErase(SL* ps, int pos)
{assert(ps);assert(pos >= 0 && pos <= ps->size);int begin = pos;while (begin < ps->size){ps->a[begin] = ps->a[begin+1];++begin;}ps->size--;
}
//顺序表的查找
//找到返回下标,找不到返回-1
int SLFind(SL* ps, SLDataType x)
{assert(ps);for (int i = 0; i < ps->size; i++){if (ps->a[i] == x){return i;}}return -1;
}

3.单链表的实现

顺序表存在下面的问题:

  • 尾插效率还不错,头插或中间插入删除,需要挪动数据,效率低
  • 满了以后只能扩容,扩容是有一定的消耗的,扩容一般存在一定的空间浪费

3.1 认识单链表

如果想要插入一个结点,就不需要挪动数据了,改指针的指向就可以了

同样我们删除结点,直接将前一个结点的指针指向后一个结点就可以了

首先我们还是从工程的角度去考虑,创建SList.h SList.c test.c三个文件

SList.h放置函数的声明

SList.c放置函数的定义

test.c进行测试 

3.2 创建单链表

3.3 单链表的操作

3.3.1 打印单链表

//打印单链表
//尽量不要动phead
void SLTPrint(SLNode* phead)
{SLNode* cur = phead;while (cur != NULL){printf("%d->", cur->val);cur = cur->next;}printf("NULL\n");
}

3.3.2 开辟新空间

//开辟新空间
SLNode* CreateNode(SLNDataType x)
{SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));if (newnode == NULL){perror("malloc fail");exit(-1);}newnode->val = x;newnode->next = NULL;return newnode;
}

3.3.3 尾插

//尾插
void SLTPushBack(SLNode** pphead, SLNDataType x)
{SLNode* newnode = CreateNode(x);if (*pphead == NULL){*pphead = newnode;}else{SLNode* tail = *pphead;while (tail->next != NULL){tail = tail->next;}tail->next = newnode;}
}

3.3.4 头插

//头插
void SLTPushFront(SLNode** pphead, SLNDataType x)
{SLNode* newnode = CreateNode(x);newnode->next = *pphead;*pphead = newnode;
}

3.3.5 尾删

//尾删
void SLTPopBack(SLNode** pphead)
{assert(*pphead);if ((*pphead)->next == NULL){free(*pphead);*pphead = NULL;}else{SLNode* prev = NULL;SLNode* tail = *pphead;while (tail->next != NULL){prev = tail;tail = tail->next;}free(tail);prev->next = NULL;}
}

3.3.6 头删

//头删
void SLTPopFront(SLNode** pphead)
{assert(*pphead);SLNode* tmp = *pphead;*pphead = (*pphead)->next;free(tmp);
}

3.3.7 查找

//查找
SLNode* SLTFind(SLNode* phead, SLNDataType x)
{SLNode* cur = phead;while (cur){if (cur->val == x){return cur;}else{cur = cur->next;}}return NULL;
}

3.3.8 在pos前面插入

//在pos前面插入
void SLTInsert(SLNode** pphead, SLNode* pos, SLNDataType x)
{assert(pphead);assert(pos);assert(*pphead);//assert((!pos && !(*pphead)) || (pos && (*pphead)));if (*pphead == pos){SLTPushFront(pphead, x);}else{SLNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}SLNode* newnode = CreateNode(x);prev->next = newnode;newnode->next = pos;}
}

3.3.9 删除pos位置

//删除pos位置
void SLTErase(SLNode** pphead, SLNode* pos)
{assert(pphead);assert(pos);assert(*pphead);if (*pphead == pos){SLTPopFront(pphead);}else{SLNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}prev->next = pos->next;free(pos);pos = NULL;}
}

3.3.10 销毁

//销毁
void SLTDestroy(SLNode** pphead)
{assert(pphead);SLNode* cur = *pphead;while (cur->next != NULL){SLNode* next = cur->next;free(cur);cur = next;}*pphead = NULL;
}

3.3.11 在pos位置之后插入

// 单链表在pos位置之后插入x
void SLTInsertAfter(SLNode* pos, SLNDataType x)
{SLNode* newnode = CreateNode(x);newnode->next = pos->next;pos->next = newnode;
}

3.3.12 删除pos位置之后的值

// 单链表删除pos位置之后的值
void SLTEraseAfter(SLNode* pos)
{assert(pos->next != NULL);pos->next = pos->next->next;free(pos->next);pos->next = NULL;
}

3.4 实现代码

3.4.1 SList.h

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
//创建单链表
typedef int SLNDataType;
typedef struct SLNode
{SLNDataType val;struct SLNode* next;
}SLNode;//打印单链表
//尽量不要动phead
void SLTPrint(SLNode* phead);
//开辟新空间
SLNode* CreateNode(SLNDataType x);//尾插
void SLTPushBack(SLNode** pphead, SLNDataType x);
//头插
void SLTPushFront(SLNode** pphead, SLNDataType x);
//尾删
void SLTPopBack(SLNode** pphead);
//头删
void SLTPopFront(SLNode** pphead);//查找
SLNode* SLTFind(SLNode* phead, SLNDataType x);
//在pos前面插入
void SLTInsert(SLNode** pphead, SLNode* pos, SLNDataType x);
//删除pos位置
void SLTErase(SLNode** pphead, SLNode* pos); // 单链表在pos位置之后插入x
void SLTInsertAfter(SLNode* pos, SLNDataType x);
// 单链表删除pos位置之后的值
void SLTEraseAfter(SLNode* pos);//销毁
void SLTDestroy(SLNode** pphead);

3.4.2 SList.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"SList.h"
//打印单链表
//尽量不要动phead
void SLTPrint(SLNode* phead)
{SLNode* cur = phead;while (cur != NULL){printf("%d->", cur->val);cur = cur->next;}printf("NULL\n");
}
//开辟新空间
SLNode* CreateNode(SLNDataType x)
{SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));if (newnode == NULL){perror("malloc fail");exit(-1);}newnode->val = x;newnode->next = NULL;return newnode;
}
//尾插
void SLTPushBack(SLNode** pphead, SLNDataType x)
{SLNode* newnode = CreateNode(x);if (*pphead == NULL){*pphead = newnode;}else{SLNode* tail = *pphead;while (tail->next != NULL){tail = tail->next;}tail->next = newnode;}
}
//头插
void SLTPushFront(SLNode** pphead, SLNDataType x)
{SLNode* newnode = CreateNode(x);newnode->next = *pphead;*pphead = newnode;
}
//尾删
void SLTPopBack(SLNode** pphead)
{assert(*pphead);if ((*pphead)->next == NULL){free(*pphead);*pphead = NULL;}else{SLNode* prev = NULL;SLNode* tail = *pphead;while (tail->next != NULL){prev = tail;tail = tail->next;}free(tail);prev->next = NULL;}
}
//头删
void SLTPopFront(SLNode** pphead)
{assert(*pphead);SLNode* tmp = *pphead;*pphead = (*pphead)->next;free(tmp);
}
//查找
SLNode* SLTFind(SLNode* phead, SLNDataType x)
{SLNode* cur = phead;while (cur){if (cur->val == x){return cur;}else{cur = cur->next;}}return NULL;
}
//在pos前面插入
void SLTInsert(SLNode** pphead, SLNode* pos, SLNDataType x)
{assert(pphead);assert(pos);assert(*pphead);//assert((!pos && !(*pphead)) || (pos && (*pphead)));if (*pphead == pos){SLTPushFront(pphead, x);}else{SLNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}SLNode* newnode = CreateNode(x);prev->next = newnode;newnode->next = pos;}
}
//删除pos位置
void SLTErase(SLNode** pphead, SLNode* pos)
{assert(pphead);assert(pos);assert(*pphead);if (*pphead == pos){SLTPopFront(pphead);}else{SLNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}prev->next = pos->next;free(pos);pos = NULL;}
}
// 单链表在pos位置之后插入x
void SLTInsertAfter(SLNode* pos, SLNDataType x)
{SLNode* newnode = CreateNode(x);newnode->next = pos->next;pos->next = newnode;
}
// 单链表删除pos位置之后的值
void SLTEraseAfter(SLNode* pos)
{assert(pos->next != NULL);pos->next = pos->next->next;free(pos->next);pos->next = NULL;
}
//销毁
void SLTDestroy(SLNode** pphead)
{assert(pphead);SLNode* cur = *pphead;while (cur->next != NULL){SLNode* next = cur->next;free(cur);cur = next;}*pphead = NULL;
}

4.带头双向循环链表的实现

4.1 认识带头双向循环链表

4.1.1 双向链表

我们之前认学习的单链表,是包含一个next指针指向下一个结点,而双向链表既有next指针,又有一个前指针指向前一个结点

4.1.2 循环链表

循环链表就是最后一个结点的next不指向NULL,指向第一个结点

4.1.3 带头链表

带头链表就是带哨兵位的头结点head,头结点不存数据

4.1.4 带头双向循环链表

  1. 无头单向非循环链表结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。
  2. 带头双向循环链表结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单

4.1.5 双向链表的优势和不足

双向链表的优势

  • 任意位置插入删除都是O(1)
  • 按需申请释放,合理利用空间,不存在浪费

问题

  • 下标的随机访问不方便O(N)

4.1.6 顺序表的优势和不足

顺序表的优势

  • 支持下标的随机访问O(1)

问题

  • 头插或中间插入的效率低O(N)
  • 空间不够需要扩容,有一定的消耗,且可能存在一定的空间浪费
  • 只适合尾插尾删

4.2 实现带头双向循环链表

同样我们创建三个文件来实现:

4.2.1 创建带头双向循环链表

我们在结构体中定义val存数据,prev指向前一个结点,next指向下一个结点

4.2.2 初始化

让phead->next和phead->prev都指向phead,给phead->val赋值为-1,最后返回phead

4.2.3 创建返回链表的头结点

4.2.4 打印链表

4.2.5 尾插

4.2.6 尾删

4.2.7 头插

4.2.8 头删

头结点不能删!!!

所以我们要assert(phead->next != phead)

4.2.9 查找

4.2.10 在pos位置前插入

特殊情况:

  • LTInsert(phead->next,x)就是头插
  • LTInsert(phead,x)就是尾插

4.2.11 删除pos位置

特殊情况:

  • LTErase(phead->next)就是头删
  • LTErase(phead->prev)就是尾删

4.2.12 销毁

4.3 实现代码

4.3.1 ListNode.h

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int LTDataType;
typedef struct ListNode
{LTDataType val;struct ListNode* prev;struct ListNode* next;
}LTNode;//初始化
LTNode* LTInit();
//创建返回链表的头结点.
LTNode* CreateLTNode(LTDataType x);
//打印
void LTPrint(LTNode* phead);//尾插
void LTPushBack(LTNode* phead, LTDataType x);
//尾删
void LTPopBack(LTNode* phead);
//头插
void LTPushFront(LTNode* phead, LTDataType x);
//头删
void LTPopFront(LTNode* phead);
//查找
LTNode* LTFind(LTNode* phead, LTDataType x);
//在pos位置前插入
void LTInsert(LTNode* pos, LTDataType x);
//删除pos位置
void LTErase(LTNode* pos);
//销毁
void LTDestroy(LTNode* phead);

4.3.2 ListNode.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"ListNode.h"
//初始化
LTNode* LTInit()
{LTNode* phead = CreateLTNode(-1);phead->next = phead;phead->prev = phead;return phead;
}
//创建返回链表的头结点.
LTNode* CreateLTNode(LTDataType x)
{LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));if (newnode == NULL){perror("malloc fail");exit(-1);}newnode->val = x;newnode->next = NULL;newnode->prev = NULL;return newnode;
}
//打印
void LTPrint(LTNode* phead)
{assert(phead);LTNode* cur = phead->next;printf("哨兵位");while (cur != phead){printf("%d<=>", cur->val);cur = cur->next;}printf("哨兵位\n");
}
//尾插
void LTPushBack(LTNode* phead, LTDataType x)
{assert(phead);LTNode* tail = phead->prev;LTNode* newnode = CreateLTNode(x);tail->next = newnode;newnode->prev = tail;phead->prev = newnode;newnode->next = phead;
}
//尾删
void LTPopBack(LTNode* phead)
{assert(phead);assert(phead->next != phead);LTNode* tail = phead->prev;LTNode* tailPrev = tail->prev;free(tail);tailPrev->next = phead;phead->prev = tailPrev;
}
//头插
void LTPushFront(LTNode* phead, LTDataType x)
{assert(phead);LTNode* newnode = CreateLTNode(x);LTNode* first = phead->next;newnode->next = first;first->prev = newnode;phead->next = newnode;newnode->prev = phead;
}
//头删
void LTPopFront(LTNode* phead)
{assert(phead);assert(phead->next != phead);LTNode* first = phead->next;LTNode* second = first->next;phead->next = second;second->prev = phead;free(first);first = NULL;
}
//查找
LTNode* LTFind(LTNode* phead, LTDataType x)
{assert(phead);LTNode* cur = phead->next;while (cur != phead){if (cur->val == x){return cur;}cur = cur->next;}return NULL;
}
//在pos位置前插入
void LTInsert(LTNode* pos, LTDataType x)
{assert(pos);LTNode* newnode = CreateLTNode(x);LTNode* posprev = pos->prev;posprev->next = newnode;newnode->prev = posprev;newnode->next = pos;pos->prev = newnode;
}
//删除pos位置
void LTErase(LTNode* pos)
{assert(pos);LTNode* posprev = pos->prev;LTNode* posnext = pos->next;posprev->next = posnext;posnext->prev = posprev;free(pos);pos = NULL;
}
//销毁
void LTDestroy(LTNode* phead)
{assert(phead);LTNode* cur = phead->next;while (cur != phead){LTNode* next = cur->next;free(cur);cur = next;}free(phead);phead = NULL;
}

5.数组下标为什么从0开始

 我们在学习数组时会有这个疑问:

数组元素的下标为什么从0开始而不从1开始呢?

从1开始更符合我们的日常习惯,比如生活中我们通常说第1个,而不是第0个

5.1 原因

对于数组元素的访问在操作系统层其实就是对特定内存偏移量的数据的访问

换而言之即如果想要访问一个数组的某一个元素的值那么首先就要计算它的地址偏移量

其大概的公式为:

a[k]_adress = base_address + k*type_size ;

倘若数组下标是从1开始那么地址计算公式即会转变为:

a[k]_adress = base_address + (k-1)*type_size ;

 这对于CPU来说多了一次减法操作

简单一句话: 就是为了方便 计算出每个元素的具体内存地址

因为数组变量 实际上在内存上储存的是这个数组变量中第一个元素的的首地址,而系统在取数组中某个元素的值时,必须要得到具体的那个元素的地址才能获取到对应的值

具体每个元素的内存地址 = 数组变量首地址 + 下标 x 每个元素占用的字节数

比如:

 int a[5]={10,11,12,13,14}

因为 int每个元素占用4个字节,所以 数组中每个相邻的元素内存地址都相差4,

那么每个元素的地址就等于前一个元素的地址 + 4

  • a[0] 的内存地址    =    a的地址 + 0 * 4  (第一个元素的地址计算结果  跟数组的首地址一样)
  • a[1] 的内存地址    =    a的地址 + 1 * 4     (下标是1,内存地址就就是首地址 偏移 4字节)
  • a[2] 的内存地址    =    a的地址 + 2 * 4    (下标是2,内存地址就首地址 偏移 8字节)
  • ..........
  • a[5]的内存地址    =    a的地址 + 5 * 4   (下标是5  内存地址就是首地址 偏移 20字节)

如果从1开始 就要减去1,多一步运算,效率自然不让直接点高

所以数组的索引(下标)从0开始是为了方便计算每个元素的地址

如果从1开始的话,运算起来没有从0开始方便

所以,大部分编程语言数组索引都是从0开始

5.2 拓展

为什么计算机语言中的下标都是从0开始的? - 知乎 (zhihu.com)

6.环型链表

6.1 环型链表是什么

 尾结点不指向NULL,指向头就是循环链表

那么带环链表就意味着尾结点的next可以指向链表的任意一个结点,甚至可以指向他自己

这里我们的算法思路唯一靠谱的还是快慢指针

slow一次走一步,fast一次走两步,当slow走到中间的时候,fast一定入环了,如果fast指向NULL,则该链表无环

当slow再走一半也就入环了,这个时候,由于slow走的慢,fast走的快,所以fast和slow最终会相遇的

6.2 快慢指针判断环形链表

我们在前面文章中写过用快慢指针判断链表是否带环:

leetcode:环形链表-CSDN博客

我们用的是slow指针一次走一步,fast指针一次走两步,当slow入环后开始了追击,每走一次距离缩短1,最终就会相遇

6.2.1 思考

但是我们思考一个问题:如果slow一次走一步,fast一次走三步,会不会相遇呢?

思考这个问题我们可以做一个假设:

假设环的长度是C,假设slow进环时,fast与slow之间的距离为N

6.2.2 推导思路

接着我们可以推一下:

如果slow一次走一步,fast一次走三步,每次追击距离缩小2

所以我们可以得出初步的结论:

  1. 如果N是偶数,就直接追上了
  2. 如果N是奇数,C是奇数,第一轮错过了,第二轮就追上了
  3. 如果N是奇数,C是偶数,就永远追不上

结论的第三条其实条件是不成立的,我们画图推一下:

所以这里我们就能得到一个结论

如果N是奇数,C是偶数,这个等式的条件是不成立的,所以不可能出第三种情况

6.2.3 结论

所以我们可以得出最终的结论:

  1. 如果N是偶数,就直接追上了
  2. 如果N是奇数,C是奇数,第一轮错过了,第二轮就追上了
  3. 不可能出现N是奇数,C是偶数的情况

所以如果slow一次走一步,fast一次走三步,一定能追上

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/767593.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

馆室一体化查档平台制度有哪些

馆室一体化查档平台制度是指图书馆或档案馆在数字化和信息化的背景下&#xff0c;建立起的集查阅、借阅、咨询、文献传递等多项功能于一体的平台制度。下面是一些常见的馆室一体化查档平台制度&#xff1a; 1. 馆藏管理制度&#xff1a;包括图书和档案的采购、编目、分类、整理…

详解rtklib中main函数如何配置文件(下)

目录 一、main函数流程总结 二、分析识别 -k 后如何配置 三、最后传参的数据文件处理方式 一、main函数流程总结 详解rtklib中main函数如何配置文件&#xff08;上&#xff09;-CSDN博客 在这片文章中讲解了rtklib中main函数的整个流程。 &#xff08;1&#xff09;通过…

最长上升子序列

一、题目描述 B3637 最长上升子序列 二、问题简析 2.1 法一&#xff1a; O ( N 2 ) O(N^2) O(N2) 令 d p [ i ] dp[i] dp[i] 以 a i a_i ai​ 结尾的上升子序列的最大长度。 以 a i a_i ai​ 结尾的上升子序列有两种可能&#xff1a; 1、仅有 a i a_i ai​ 一个元素2…

保研复习概率论2

1.什么是随机变量的数学期望&#xff08;expected value&#xff09;&#xff1f; 如果X是离散型随机变量&#xff0c;其分布列为piP{Xxi}(i1,2...)&#xff0c;若级数绝对收敛&#xff0c;则称随机变量X的数学期望存在&#xff0c;并将的和称为随机变量X的数学期望&#xff0…

5.5.7、【AI技术新纪元:Spring AI解码】Redis

Redis Redis 是一款开源(BSD 许可)的内存数据结构存储系统,可用作数据库、缓存、消息代理以及流处理引擎。Redis 提供了诸如字符串、哈希表、列表、集合、带范围查询的有序集合、位图、HyperLogLogs、地理空间索引和流等多种数据结构。 Redis 向量搜索 Redis 向量搜索与查…

【C++】每日一题 位1的个数

编写一个函数&#xff0c;输入是一个无符号整数&#xff08;以二进制串的形式&#xff09;&#xff0c;返回其二进制表达式中数字位数为 ‘1’ 的个数&#xff08;也被称为汉明重量&#xff09;。 #include <iostream> #include <cstdint>int hammingWeight(uint3…

汇编语言中的MVC

一 MVC指令 1&#xff0e;移动字符串指令MVC 移动字符串指令MVC的格式为&#xff1a; MVC D1(L,B1),D2(B2) (移动字符串) 功能&#xff1a;&#xff08;D1&#xff08;B1&#xff09;&#xff09; ← &#xff08;D2&#xff08;B2&#xff09;&#xff09; L个字符 指令的执行…

React面试总结

React中JSX转换为真实Dom的过程 可以从几个大体过程来看 jsx语法解析 jsx语法解析成js代表的对象&#xff0c;即把jsx语法转换为基于js的React.createElement(args1,args2,args3)&#xff0c;args1,args2,args3分别是标签类型、属性和标签内容&#xff0c;主要利用的是babel等…

leetcode 316.去除重复字母

思路&#xff1a;贪心单调栈。 这道题和前几天做的那道“删除k位数字”那道题很像。 这里由于是按照字典序进行输出的&#xff0c;而且删除的地方我们也不知道&#xff0c;是随机的&#xff0c;这个时候其实就应该想到用单调栈进行解答&#xff0c;其实这样才能进行很好的存储…

COPY requires at least two arguments, docker COPY 报错

COPY requires at least two arguments # 使用 Node.js 12.16.0FROM node:12.16.0WORKDIR /appCOPY ..原因&#xff1a;Dockerfile文件COPY后的两个. 要加空格 本内容来源于小豆包&#xff0c;想要更多内容请跳转小豆包 》

【Frida】【Android】01_手把手教你环境搭建

▒ 目录 ▒ &#x1f6eb; 导读开发环境 1️⃣ 环境搭建安装Android模拟器安装Frida CLI安装Frida Server端口重定向&#xff1a;adb forward 2️⃣ 运行测试spwan模式attach模式直接加载脚本 &#x1f4d6; 参考资料 &#x1f6eb; 导读 开发环境 版本号描述文章日期2024-03…

C++按位运算

6.3 按位运算 6.3.1 位运算概述 符号描述运算规则实例&与两个位都为1时&#xff0c;结果才为1。0001&00011,0001&00000,0000&00000000|或两个位都为0时&#xff0c;结果才为0。0001^异或两个位相同为0&#xff0c;相异为1。0001∧00010000,0001∧00001,0000∧…

matlab空间曲线图形

说明&#xff1a;问题来自CSDN-问答板块&#xff0c;题主提问。 需求&#xff1a;如何用子图命令画出平面y2z&#xff0c;z2y与球面x^2y^2z^25相交的空间曲线图形。需要完整代码和结果的图片。 一、先看效果图 二、代码 % 创建figure figure% 创建二维网格&#xff0c;用于定…

element ui实践bug

文章目录 el-table的默认全部展开属性default-expand-all el-table的默认全部展开属性default-expand-all 该属性只有table 初始化时才会生效&#xff0c;后续动态更改无效。 如果想要动态修改default-expand-all 属性&#xff0c;则需要控制table 的重新渲染&#xff0c;可以…

day01_mysql数据类型和运算符_课后练习 - 参考答案

文章目录 day01_mysql_课后练习第1题第2题第3题第4题第5题 day01_mysql_课后练习 第1题 案例&#xff1a; 1、创建数据库day01_test01_library 2、创建表格books 字段名字段说明数据类型允许为空唯一b_id书编号int(11)否是b_name书名varchar&#xff08;50&#xff09;否否…

性能调优专题并发编程专题(持续更新)

一、性能调优专题 MySQL相关 一、深入理解MySQL索引底层数据结构与算法 索引概念&#xff1a;索引是帮助MySQL高效获取数据的排好序的数据结构 索引数据结构&#xff1a; 1、二叉树 缺点&#xff1a;当索引字段有序的时候&#xff0c;不会自动平衡二叉树&#xff0c;数据…

JavaScript单元测试jasmine学习(一)

介绍&#xff1a; jasmine是用于测试JavaScript的一种测试框架,BDD(Behavior Driven Development)行为驱动开发。不依赖于任何其他JavaScript框架&#xff0c;也不需要DOM 准备工作&#xff1a; 1. 首先添加jasmine到自己的项目中 npm install --save-dev jasmine 2. 在项目…

【热门话题】ECMAScript vs JavaScript:理解两者间的联系与区别

&#x1f308;个人主页: 鑫宝Code &#x1f525;热门专栏: 闲话杂谈&#xff5c; 炫酷HTML | JavaScript基础 ​&#x1f4ab;个人格言: "如无必要&#xff0c;勿增实体" 文章目录 ECMAScript vs JavaScript&#xff1a;理解两者间的联系与区别1. ECMAScript&am…

小目标检测常见解决策略总结

1. 引言 尽管目标检测算法取得了长足的发展&#xff0c;例如 Faster RCNN、YOLO、SSD、RetinaNet、EfficientDet 等。通常&#xff0c;这些模型是在 COCO数据集上训练的。它是一个包含各种对象类别和标注的大规模数据集&#xff0c;因此在训练对象检测器方面很受欢迎。然而&am…

基础语法——字符串

字符串数组的常用操作 需引用头文件#include<cstring> #include<iostream> #include<cstring>using namespace std;int main(){char str[100];char a[10],b[10];strlen(str); //求字符串的长度&#xff0c;不包括\0 ,O(n)strcmp(a,b); //按字典序比较a,b的…