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

主页:醋溜马桶圈-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;通过…

保研复习概率论2

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

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

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

matlab空间曲线图形

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

【热门话题】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…

应急响应实战笔记04Windows实战篇(1)

第1篇&#xff1a;FTP暴力破解 0x00 前言 ​ FTP是一个文件传输协议&#xff0c;用户通过FTP可从客户机程序向远程主机上传或下载文件&#xff0c;常用于网站代码维护、日常源码备份等。如果攻击者通过FTP匿名访问或者弱口令获取FTP权限&#xff0c;可直接上传webshell&#…

RuoYi-Vue-Plus(基础知识点jackson、mybatisplus、redis)

一、JacksonConfig 全局序列化反序列化配置 1.1yml中配置 #时区 spring.jackson.time-zoneGMT8 #日期格式 spring.jackson.date-formatyyyy-MM-dd HH:mm:ss #默认转json的属性&#xff0c;这里设置为非空才转json spring.jackson.default-property-inclusionnon_null #设置属性…

直接插入排序(六大排序)

本期讲解排序与六大排序中的希尔排序 —————————————————————— 1.排序的概念及其运用 1.1排序的概念 排序&#xff1a;所谓排序&#xff0c;就是使一串记录&#xff0c;按照其中的某个或某些关键字的大小&#xff0c;递增或递减的排列起来的操作。稳定性…

win10开启了hyper-v,docker 启动还是报错 docker desktop windows hypervisor is not present

问题 在安装了docker windows版本后启动 docker报错docker desktop windows hypervisor is not present 解决措施 首先确认windows功能是否打开Hyper-v 勾选后重启&#xff0c;再次启动 启动后仍报这个错误&#xff0c;是Hyper-v没有设置成功 使用cmd禁用再启用 一.禁用h…

Activiti7学习大纲及环境-Activiti7从入门到专家(2)

学习大纲 入门系列 开发环境及源码编译流程设计器核心API简单流程示例启动与结束事件边界事件中间事件用户任务手动任务接受任务服务任务脚本任务业务规则任务排他网关并行网关包容网关事件网关子流程调用活动泳池泳道执行监听器任务监听器全局监听器真实业务流程 进阶系列 …

基于react native的自定义轮播图

基于react native的自定义轮播图 效果示例图示例代码 效果示例图 示例代码 import React, {useEffect, useRef, useState} from react; import {Animated,PanResponder,StyleSheet,Text,View,Dimensions, } from react-native; import {pxToPd} from ../../common/js/device;c…

序列化与反序列化介绍

文章目录 一、序列化与反序列化二、PHP反序列化漏洞成因三、JAVA反序列化 一、序列化与反序列化 在PHP语言开发层面上基本都是围绕着serialize()&#xff0c;unserialize()这两个函数。serialize()函数序列化对象后&#xff0c;可以很方便的将它传递给其他需要它的地方&#x…

存储器的层次结构和局部性原理

前言 大家好我是jiantaoyab&#xff0c;这是我所总结作为学习的笔记第19篇&#xff0c;在这里分享给大家&#xff0c;这篇文章讲存储器的一部分内容。 存储器的层次结构 SRAM 静态随机存取存储器的芯片&#xff0c;SRAM 之所以被称为“静态”存储器&#xff0c;是因为只要处…

用Springboot(java程序)访问Salesforce RestAPI

本文讲一下&#xff0c;如何从0构建一个Springboot的应用程序&#xff0c;并且和Salesforce系统集成&#xff0c;取得Salesforce里面的数据。 一、先在Salesforce上构建一个ConnectApp。 有了这个&#xff0c;SF才允许你和它集成。手顺如下&#xff1a; 保存后&#xff0c;…

jenkins配置源码管理的git地址时,怎么使用不了 credential凭证信息

前提 Jenkins使用docker部署 问题 &#xff08;在jenlins中设置凭证的方式&#xff09;在Jenkins的任务重配置Git地址&#xff0c;并且设置了git凭证,但是验证不通过&#xff0c;报错; 无法连接仓库&#xff1a;Command "git ls-remote -h -- http://192.1XX.0.98:X02/…

模拟实现字符串库函数(一)

在C语言的标准库中提供了很多针对字符串的库函数&#xff0c;这篇文章我们会学习并模拟实现几个简单的库函数 求字符串长度函数strlen strlen函数我们在之前已经用过很多次了&#xff0c;同时也模拟实现过&#xff0c;但是都不是模仿标准库中的strlen来实现&#xff0c;首先我…