【数据结构】一篇带你彻底吃透 顺序表

简介: 【数据结构】一篇带你彻底吃透 顺序表

1 - 顺序表的概念及结构



顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改等功能。


  • 顺序表一般可以分为:
  1. 静态顺序表:使用定长数组存储元素。
  2. 动态顺序表:使用动态开辟的数组存储。


而现实的顺序表大多数采用动态的,因为静态顺序表有几个缺点,当然还有优点.我们下面就展开讲讲


静态顺序表缺点:

  1. 插入和删除操作需要移动大量的元素
  2. 当线性表长度变化较大时,难以确定存储空间的容量
  3. 造成存储空间的 “碎片”


静态顺序表优点:

  1. 可以快速访问任一元素


静态顺序表只适用于确定知道需要存多少数据的场景。静态顺序表的定长数组导致定大了,空间开多了浪费,开少了不够用。所以现实中基本都是使用动态顺序表,根据需要动态的分配空间大小。


相对总体来说静态顺序表是不够优的,所有该文章我们就讲解动态顺序表,当然最后还会附上静态顺序表的全代码.


  • 存储顺序表结构需要三个属性
  1. 存储空间的起始位置:数据data .
  2. 线性表的存储容量: capacity
  3. 线性表的当前长度: sz


struct SeqListNode
{
  STDataType* data; 
  int sz;       //顺序表元素个数
  int capacity; //顺序表容量
};


2 - 动态顺序表接口实现



存储结构三个属性


typedef int STDataType; //方便以后要存储其它类型。
//动态通讯录
typedef struct SeqListNode
{
  STDataType* data;  //动态开辟的数组
  int sz;  //顺序表元素个数
  int capacity; //顺序表容量
}SeqNode;


初始化顺序表


//初始化顺序表
void Seqinit(SeqNode* head)
{
  assert(head); //断言:判断是否为空指针
  head->data = NULL;  //首先指向NULL
  head->sz = 0;       //初始顺序个数为0
  head->capacity = 0; //初始容量为0
}


顺序表增容


顺序表添加元素时,都需检测一下是否要增容。不然会出现越界情况。

每次扩容为原来的2倍,如扩容太大会浪费空间。


void CheckCapacity(SeqNode* head)
{
  //断言:判断是否为空指针
  assert(head);
  int newcapacity = 0;//新容量
  if (head->sz == head->capacity)
  {
    if (head->capacity == 0)
      newcapacity = head->capacity = 4; //首次扩容为4
    else
      newcapacity = head->capacity * 2; //扩容为原来的2倍
    //扩容
    STDataType* tmp = NULL;
    if (head->data == NULL)
      tmp = (STDataType*)malloc(newcapacity * sizeof(STDataType)); //首次扩容
    else
      tmp = (STDataType*)realloc(head->data, sizeof(STDataType) * newcapacity); //再次扩容
    //检测扩容是否成功
    if (tmp == NULL)
    {
      //如果扩容失败就退出程序
      perror(" Capacityfile: ");
      exit(-1);
    }
    //扩容成功把内容给回顺序表属性
    head->data = tmp;
    head->capacity = newcapacity;
  }
}


顺序表尾插


//顺序表尾插
void SeqPushBack(SeqNode* head, STDataType x)
{
  assert(head);     //断言:判断是否为空指针
  CheckCapacity(head);  //检测是否需要扩容
  head->data[head->sz] = x;  //要插入的数据
  head->sz++;  //顺序表容量+1
}


顺序表头插


头插算法思路

  1. 从最后个元素开始向前遍历到第0个位置,分别向后挪动一个位置.
  2. 将要插入元素填入首元素位置处
  3. 表长+1.


//顺序表头插
void SeqPushFront(SeqNode* head, STDataType x)
{
  assert(head);//断言:判断是否为空指针
  CheckCapacity(head);  //检测是否需要扩容
  //从前往后递推覆盖
  int i = head->sz - 1;
  for (i; i >= 0; i--)
  {
    head->data[i + 1] = head->data[i]; //将数据【0 - sz-1】向后递推覆盖
  }
  head->data[0] = x; //进行头插
  head->sz++;  //顺序表容量+1
}


顺序表尾删


直接把表长减一即可


//顺序表尾删
void SeqPopBack(SeqNode* head)
{
  assert(head);//断言:判断是否为空指针
  if (head->sz == 0) //判断顺序表是否有元素
  {
    printf("该顺序表为空,无法删除\n");
    return;
  }
  head->sz--;  //顺序表容量-1
}


顺序表头删


  1. 从第二个元素开始到最后一个元素,分别将他们都向前移动一个位置。
  2. 表长在减1。


//顺序表头删
void SeqPopFront(SeqNode* head)
{
  assert(head);//断言:判断是否为空指针
  if (head->sz == 0) //判断顺序表是否有元素
  {
    printf("该顺序表为空,无法删除\n");
    return;
  }
  //从后往前递推覆盖
  int i = 0;
  for (i = 0; i > head->sz-1; i--)
  {
    head->data[i] = head->data[i + 1]; //将数据从【1-sz-1】向前覆盖
  }
  head->sz--;  //顺序表容量-1
}


顺序表查找


只要遍历顺序表找到需要查找的值,如果找到返回他的下标即可.

找不到返回-1,


int SeqFind(const SeqNode* head, STDataType x)
{
  assert(head);//断言:判断是否为空指针
  if (head->sz == 0) //判断顺序表是否有元素
  {
    printf("该顺序表为空,无法查找\n");
    return -1;
  }
  int i = 0;
  for (i = 0; i < head->sz; i++)
  {
    if (head->data[i] == x) //查找到,就返回该元素下标
    {
      return i;
    }
  }
  return -1; //查找失败 返回-1
}


顺序表中删除指定下标数据


删除算法思路:

  1. 如果删除位置不合理,抛出异常.
  2. 从删除元素位置开始遍历到最后一个元素位置,分别将他们都向前挪动一个位置.
  3. 表长减1


//在顺序表中删除指定下标位置的数据
void SeqErase(SeqNode* head, int pos)
{
  assert(head);//断言:判断是否为空指针
  if (pos < 0 || head->sz == 0)
  {
    printf(" 要删除下标非法或者顺序表为空,删除失败\n");
    return ;
  }
  int i = pos;
  //将要删除的位置把后面的覆盖过来,最后在减
  for (i; i < head->sz-1; i++)
  {
    head->data[i] = head->data[i+1]; //将【pos+1 - sz-1】的数据向前挪动一个位置
  }
  head->sz--;//顺序表容量-1
}


在顺序表指定下标位置插入数据


插入算法的思路

  1. 如果删除位置不合理,抛出异常
  2. 从最后一个元素开始向前遍历到要插入的位置,分别将他们都向后移动一个位置
  3. 将元素添加到要插入的位置
  4. 表长+1


void SeqInsert(SeqNode* head, int pos, STDataType x)
{
  assert(head); //断言:判断是否为空指针
    void Seqmodify(SeqNode* head, int pos, STDataType x);
  if (pos < 0 )
  {
    printf(" 下标非法,删除失败\n");
    return;
  }
  CheckCapacity(head);  //检测是否需要扩容
  //将顺序表【pos - sz-1】从最后一个元素开始依次挪动到后一位
  int i = head->sz-1;
  for (i; i >= pos; i--)
  {
    head->data[i+1] = head->data[i]; //依次挪动到后一位,直到pos位置挪动到后一位
  }
  head->data[pos] = x; //插入数据
  head->sz++;    //顺序表容量+1
}


修改指定下标的值


//修改指定下标的值
void Seqmodify(SeqNode* head, int pos, STDataType x)
{
  assert(head);//断言:判断是否为空指针
  if (pos < 0 || head->sz == 0)
  {
    printf(" 要修改的下标非法或者顺序表为空,修改失败\n");
    return;
  }
  head->data[pos] = x;//修改数据;
}


获得顺序表个数


//查顺序表中有效数据个数
int Seqsize(const SeqNode* head)
{
  assert(head);//断言:判断是否为空指针
  return head->sz; //返回顺序表有效数据个数
}


顺序表打印


//顺序表打印
void Seqprint(const SeqNode* head)
{
  assert(head); //断言:判断是否为空指针
  if (head->sz == 0)
  {
    printf("顺序表为空,无法打印\n");
    return;
  }
  int i = 0;
  for (i = 0; i < head->sz; i++)
  {
    printf("%d ", head->data[i]);
  }
  printf("\n");
}


顺序表销毁


//顺序表销毁
void SeqDestroy(SeqNode* head)
{
  assert(head); //断言:判断是否为空指针
  free(head->data);
  head->data = NULL;  
  head->sz = 0;   
  head->capacity = 0;
}


3 - 动态顺序表全代码



SeqLish.h


#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int STDataType;
//动态通讯录
typedef struct SeqListNode
{
  STDataType* data; 
  int sz;  //顺序表元素个数
  int capacity; //顺序表容量
}SeqNode;
//初始化链表
void Seqinit(SeqNode* head);
//增容
void CheckCapacity(SeqNode* head);
//顺序表尾插
void SeqPushBack(SeqNode* head, STDataType x);
//顺序表打印
void Seqprint(const SeqNode* head);
//顺序表销毁
void SeqDestroy(SeqNode* head);
//顺序表头插
void SeqPushFront(SeqNode* head, STDataType x);
//顺序表尾删
void SeqPopBack(SeqNode* head);
//顺序表头删
void SeqPopFront(SeqNode* head);
//顺序表查找
int SeqFind(const SeqNode* head, STDataType x);
//在顺序表中删除指定下标位置的数据
void SeqErase(SeqNode* head, int pos);
//在顺序表指定下标位置插入数据
void SeqInsert(SeqNode* head, int pos, STDataType x);
//修改指定下标的值
void Seqmodify(SeqNode* head, int pos, STDataType x);
//查顺序表中有效数据个数
int Seqsize(const SeqNode* head);


SeqLish.c


#define _CRT_SECURE_NO_WARNINGS 1
#include"SeqList.h"
//初始化链表
void Seqinit(SeqNode* head)
{
  head->data = NULL;  //指向动态开辟的数组
  head->sz = 0;       //初始顺序个数为0
  head->capacity = 0; //初始容量为0
}
//顺序表销毁
void SeqDestroy(SeqNode* head)
{
  assert(head); //断言:判断是否为空指针
  free(head->data);
  head->data = NULL;  
  head->sz = 0;   
  head->capacity = 0;
}
//增容
void CheckCapacity(SeqNode* head)
{
  //断言:判断是否为空指针
  assert(head);
  int newcapacity = 0;//新容量
  if (head->sz == head->capacity)
  {
    if (head->capacity == 0)
      newcapacity = head->capacity = 4; //首次扩容为4
    else
      newcapacity = head->capacity * 2; //扩容为原来的2倍
    //扩容
    STDataType* tmp = NULL;
    if (head->data == NULL)
      tmp = (STDataType*)malloc(newcapacity * sizeof(STDataType)); //首次扩容
    else
      tmp = (STDataType*)realloc(head->data, sizeof(STDataType) * newcapacity); //再次扩容
    //检测扩容是否成功
    if (tmp == NULL)
    {
      //如果扩容失败就退出程序
      perror(" Capacityfile: ");
      exit(-1);
    }
    //扩容成功
    head->data = tmp;
    head->capacity = newcapacity;
  }
}
//顺序表尾插
void SeqPushBack(SeqNode* head, STDataType x)
{
  assert(head);     //断言:判断是否为空指针
  CheckCapacity(head);  //检测是否需要扩容
  head->data[head->sz] = x;  //要插入的数据
  head->sz++;  //顺序表容量+1
}
//顺序表打印
void Seqprint(const SeqNode* head)
{
  assert(head); //断言:判断是否为空指针
  if (head->sz == 0)
  {
    printf("顺序表为空,无法打印\n");
    return;
  }
  int i = 0;
  for (i = 0; i < head->sz; i++)
  {
    printf("%d ", head->data[i]);
  }
  printf("\n");
}
//顺序表头插
void SeqPushFront(SeqNode* head, STDataType x)
{
  assert(head);//断言:判断是否为空指针
  CheckCapacity(head);  //检测是否需要扩容
  //从前往后递推覆盖
  int i = head->sz - 1;
  for (i; i >= 0; i--)
  {
    head->data[i + 1] = head->data[i]; //将数据【0 - sz-1】向后递推覆盖
  }
  head->data[0] = x; //进行头插
  head->sz++;  //顺序表容量+1
}
//顺序表尾删
void SeqPopBack(SeqNode* head)
{
  assert(head);//断言:判断是否为空指针
  if (head->sz == 0) //判断顺序表是否有元素
  {
    printf("该顺序表为空,无法删除\n");
    return;
  }
  head->sz--;  //顺序表容量-1
}
//顺序表头删
void SeqPopFront(SeqNode* head)
{
  assert(head);//断言:判断是否为空指针
  if (head->sz == 0) //判断顺序表是否有元素
  {
    printf("该顺序表为空,无法删除\n");
    return;
  }
  //从后往前递推覆盖
  int i = 0;
  for (i = 0; i > head->sz-1; i--)
  {
    head->data[i] = head->data[i + 1]; //将数据从【1-sz-1】向前覆盖
  }
  head->sz--;  //顺序表容量-1
}
//顺序表查找
int SeqFind(const SeqNode* head, STDataType x)
{
  assert(head);//断言:判断是否为空指针
  if (head->sz == 0) //判断顺序表是否有元素
  {
    printf("该顺序表为空,无法查找\n");
    return -1;
  }
  int i = 0;
  for (i = 0; i < head->sz; i++)
  {
    if (head->data[i] == x) //查找到,就返回该元素下标
    {
      return i;
    }
  }
  return -1; //查找失败 返回-1
}
//在顺序表中删除指定下标位置的数据
void SeqErase(SeqNode* head, int pos)
{
  assert(head);//断言:判断是否为空指针
  if (pos < 0 || head->sz == 0)
  {
    printf(" 要删除下标非法或者顺序表为空,删除失败\n");
    return ;
  }
  int i = pos;
  //将要删除的位置把后面的覆盖过来,最后在减
  for (i; i < head->sz-1; i++)
  {
    head->data[i] = head->data[i+1]; //将【pos+1 - sz-1】的数据向前挪动一个位置
  }
  head->sz--;//顺序表容量-1
}
//在顺序表指定下标位置插入数据
void SeqInsert(SeqNode* head, int pos, STDataType x)
{
  assert(head); //断言:判断是否为空指针
    void Seqmodify(SeqNode* head, int pos, STDataType x);
  if (pos < 0 )
  {
    printf(" 下标非法,删除失败\n");
    return;
  }
  CheckCapacity(head);  //检测是否需要扩容
  //将顺序表【pos - sz-1】从最后一个元素开始依次挪动到后一位
  int i = head->sz-1;
  for (i; i >= pos; i--)
  {
    head->data[i+1] = head->data[i]; //依次挪动到后一位,直到pos位置挪动到后一位
  }
  head->data[pos] = x; //插入数据
  head->sz++;    //顺序表容量+1
}
//修改指定下标的值
void Seqmodify(SeqNode* head, int pos, STDataType x)
{
  assert(head);//断言:判断是否为空指针
  if (pos < 0 || head->sz == 0)
  {
    printf(" 要修改的下标非法或者顺序表为空,修改失败\n");
    return;
  }
  head->data[pos] = x;//修改数据;
}
//查顺序表中有效数据个数
int Seqsize(const SeqNode* head)
{
  assert(head);//断言:判断是否为空指针
  return head->sz; //返回顺序表有效数据个数
}


4 - 静态顺序表全代码



SeqLish.h


#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#define Max 100
typedef int STDataType;
//静态顺序表
typedef struct SeqListNode
{
  STDataType data[Max]; 
  int sz;    //顺序表数量
  int capacity;  //顺序表容量
}SeqNode;
//初始化
void Seqinit(SeqNode* head);
//增容
void CheckCapacity(SeqNode* head);
//顺序表尾插
void SeqPushBack(SeqNode* head, STDataType x);
//顺序表头插
void SeqPushFront(SeqNode* head, STDataType x);
//顺序表尾删
void SeqPopBack(SeqNode* head);
//顺序表头删
void SeqPopFront(SeqNode* head);
//顺序表打印
void Seqprint(const SeqNode* head);
//顺序表销毁
void SeqDestory(SeqNode* head);
//顺序表查找
int SeqFind(const SeqNode* head, STDataType x);
//在顺序表中删除指定下标位置的数据
void SeqErase(SeqNode* head, int pos);
//在顺序表指定下标位置插入数据
void SeqInsert(SeqNode* head, int pos, STDataType x);
//修改指定下标的值
void Seqmodify(SeqNode* head, int pos, STDataType x);
//查顺序表中有效数据个数
int Seqsize(const SeqNode* head);


SeqLish.c


#include"SeqList.h"
//顺序表初始化
void Seqinit(SeqNode* head)
{
  assert(head);
  head->capacity = 0;
  head->sz = 0;
}
//是否需要增容
void CheckCapacity(SeqNode* head)
{
  assert(head);
  if (head->sz == head->capacity)
  {
    if (head->capacity == Max)
    {
      perror("通讯录已满,无法增容:");
      exit(-1);
    }
    if (head->capacity == 0)
      head->capacity = 4;
    else
    {
      head->capacity *= 2;
      if (head->capacity > Max)
        head->capacity = Max;
    }
  }
}
//顺序表打印
void Seqprint(const SeqNode* head)
{
  assert(head);
  if (head->sz == 0)
  {
    perror("顺序表元素为空,请重新增加元素\n");
    return;
  }
  int i = 0;
  for (i = 0; i < head->sz; i++)
  {
    printf("%d ", head->data[i]);
  }
  printf("\n");
}
//顺序表尾插
void SeqPushBack(SeqNode* head, STDataType x)
{
  assert(head);
  CheckCapacity(head);
  head->data[head->sz] = x;
  head->sz++;
}
//顺序表头插
void SeqPushFront(SeqNode* head, STDataType x)
{
  assert(head);
  CheckCapacity(head);
  int i = head->sz - 1;
  for (i; i >= 0; i--)
  {
    head->data[i + 1] = head->data[i];
  }
  head->data[0] = x;
  head->sz++;
}
//顺序表尾删
void SeqPopBack(SeqNode* head)
{
  assert(head);
  head->sz--;
}
//顺序表头删
void SeqPopFront(SeqNode* head)
{
  assert(head);
  int i = 0;
  for (i = 1; i < head->sz; i++)
  {
    head->data[i-1] = head->data[i];
  }
  head->sz--;
}
//顺序表销毁
void SeqDestory(SeqNode* head)
{
  head->capacity = 0;
  head->sz = 0;
}
//顺序表查找
int SeqFind(const SeqNode* head, STDataType x)
{
  assert(head);
  if (head->sz == 0)
  {
    perror("顺序表为空\n");
    return -1;
  }
  int i = 0;
  for (i = 0; i < head->sz; i++)
  {
    //找到返回下标
    if (head->data[i] == x)
    {
      return i;
    }
  }
  return -1;
}
//在顺序表中删除指定下标位置的数据
void SeqErase(SeqNode* head, int pos)
{
  assert(head);
  if (head->sz == 0)
  {
    perror("顺序表为空\n");
    return;
  }
  int i = pos;
  for (i; i < head->sz-1; i++)
  {
    head->data[i] = head->data[i + 1];
  }
  head->sz--;
}
//在顺序表指定下标位置插入数据
void SeqInsert(SeqNode* head, int pos, STDataType x)
{
  assert(head);
  CheckCapacity(head);
  int i = head->sz - 1;;
  for (i; i >= pos ; i--)
  {
    head->data[i+1] = head->data[i];
  }
  head->data[pos] = x;
  head->sz++;
}
//修改指定下标的值
void Seqmodify(SeqNode* head, int pos, STDataType x)
{
  assert(head);
  if (head->sz == 0)
  {
    perror("顺序表为空,无法修改\n");
    return;
  }
  head->data[pos] = x;
}
//查顺序表中有效数据个数
int Seqsize(const SeqNode* head)
{
  assert(head);
  return head->sz;
}



目录
相关文章
|
20天前
|
存储 编译器 C语言
数据结构-顺序表详解(看这篇就足够了,哈哈哈)
数据结构-顺序表详解(看这篇就足够了,哈哈哈)
46 2
|
1天前
|
存储 算法 安全
2024重生之回溯数据结构与算法系列学习之顺序表【无论是王道考研人还真爱粉都能包会的;不然别给我家鸽鸽丢脸好嘛?】
顺序表的定义和基本操作之插入;删除;按值查找;按位查找等具体详解步骤以及举例说明
|
1天前
|
存储 C语言
【数据结构】顺序表(c语言实现)(附源码)
本文介绍了线性表和顺序表的基本概念及其实现。线性表是一种有限序列,常见的线性表有顺序表、链表、栈、队列等。顺序表是一种基于连续内存地址存储数据的数据结构,其底层逻辑是数组。文章详细讲解了静态顺序表和动态顺序表的区别,并重点介绍了动态顺序表的实现,包括初始化、销毁、打印、增删查改等操作。最后,文章总结了顺序表的时间复杂度和局限性,并预告了后续关于链表的内容。
10 3
|
1天前
|
算法 安全 NoSQL
2024重生之回溯数据结构与算法系列学习之顺序表习题精讲【无论是王道考研人还真爱粉都能包会的;不然别给我家鸽鸽丢脸好嘛?】
顺序表的定义和基本操作之插入;删除;按值查找;按位查找习题精讲等具体详解步骤以及举例说明
|
21天前
|
存储 Java
数据结构第二篇【关于java线性表(顺序表)的基本操作】
数据结构第二篇【关于java线性表(顺序表)的基本操作】
27 6
|
21天前
|
存储 安全 Java
【用Java学习数据结构系列】探索顺序表和链表的无尽秘密(附带练习唔)pro
【用Java学习数据结构系列】探索顺序表和链表的无尽秘密(附带练习唔)pro
20 3
|
21天前
|
存储 C语言
探索C语言数据结构:利用顺序表完成通讯录的实现
本文介绍了如何使用C语言中的顺序表数据结构实现一个简单的通讯录,包括初始化、添加、删除、查找和保存联系人信息的操作,以及自定义结构体用于存储联系人详细信息。
18 2
|
24天前
|
存储 算法 索引
【数据结构】——顺序表
【数据结构】——顺序表
|
24天前
|
存储
【数据结构】线性表和顺序表
【数据结构】线性表和顺序表
20 1
|
26天前
|
存储
数据结构1——顺序表
数据结构1——顺序表
17 1