数据结构与算法——线性顺序表原理及C语言实现
参考博文:程序内功篇二–线性顺序表
一、线性表顺序存储概念
- 线性表示包含若干个数据元素的一个线性序列,记为:L=(a0,a1, … ai-1,ai,ai+1,…an-1),其中L为表名, ai为数据元素, n为表长
- 若将线性表L=(a0,a1, …,an-1)中的各元素依次存储于计算机一片连续的存储空间。
线性表的特征:
- 对非空表,a0 是表头,无前驱
- an-1是表尾,无后继
- 其他的每个元素 ai 有且仅有一个直接前驱 ai-1,和一个直接后继 ai+1
顺序存储结构的特点:
- 逻辑上相邻的元素ai,ai+1,其存储位置也是相邻的
- 对数据元素ai的存取为随机存取或按地址存取
- 存储密度高
存储密度D=(数据结构中元素所占存储空间)/(整个数据结构所占空间)
顺序存储结构的表示:
在C语言中,可借助于结构体配合一维数组类型来描述线性表的顺序存储结构:
//变量与顺序表结构定义
#define N 100
typedef int data_t;
typedef struct{
data_t data[N]; //表的存储空间
int n; //线性表中最后一个元素下标
}sqlist;
注意: n表示线性表最后一个元素的下标
二、顺序表的基本属性程序设计
(1)线性表的创建 list_create
线性表的创建主要分为申请内存空间、初始化变量、返回顺序表指针三大步骤。
- 功能:建立一个空的线性表
- 参数:void
- 返回值:线性表指针
/* 功能:建立一个空的线性表 参数:void 返回值:线性表指针 */ sqlist* list_create() { //申请内存空间 sqlist* list = (sqlist *)malloc(sizeof(sqlist)); if(list == NULL){ printf("list_create: malloc error\n"); return NULL; } //线性表成员变量赋值 list->n = -1; return list; }
(2)清空顺序表 list_clear
清空顺序表主要对顺序表内部数据进行初始化,对结尾数据下标索引置-1:
- 功能:清空线性表
- 参数:线性表指针
- 返回值:-1:参数错误 成功:0
/* 功能:清空线性表 参数:线性表指针 返回值:-1---参数错误 成功返回 0 */ int list_clear(sqlist* list) { //入口 参数检查 if(list == NULL){ printf("list_clear: list is NULL"); return -1; } //清空数组数据 int len = list_length(list); memset(list->data, '\0', sizeof(data_t) * len); list->n = -1; return 0; }
(3)判断是否为空表 list_empty
判断顺序表是否为空表的关键是:判断结尾数据的索引是否为-1
- 功能:判断线性表是否为空
- 参数:线性表指针
- 返回值:空:1 非空:0
/* 功能:判断线性表是否为空 参数:线性表指针 返回值:空:返回1 非空: 返回0 */ int list_empty(sqlist* list) { if(list->n == -1) return 1; else return 0; }
(4)获取线性表长度 list_length
线性表的长度即是 最后一个元素索引+1
- 功能:求线性表长度
- 参数:线性表指针
返回值:-1:参数错误 >0: 表长/* 功能:求线性表长度 参数:线性表指针 返回值:-1---参数错误 >0 表长 */ int list_length(sqlist* list) { //入口 参数检查 if(list == NULL){ printf("list_length: list is NULL"); return -1; } //返回线性表长度 return list->n + 1; }
(5)显示线性表 list_show
通过遍历线性表内连续空间实现对线性表的呈现
- 功能:显示线性表
- 参数:线性表指针
- 返回值:void
/* 功能:显示线性表 参数:线性表指针 返回值:void */ void list_show(sqlist* list) { if(list->n == -1) printf("list is NULL"); if(list != NULL) { for(int i = 0; i <= list->n; i++) printf("%d ",list->data[i]); printf("\n"); } else printf("list is NULL\n"); }
(6)释放线性表内存空间 list_free
通过遍历线性表实现内存的释放
- 功能:释放线性表内存空间
- 参数:线性表指针
- 返回值:-1: 参数错误/为空 0:释放成功
/* 功能:释放线性表内存表空间 参数:线性表指针 返回值:-1: 参数错误/为空 0: 释放成 */ int list_free(sqlist* list) { //入口参数检查 if(list == NULL) return -1; free(list); //释放内存 list = NULL; //避免成为野指针 return 0; }
三、顺序表的相关运算程序设计
(1)获取线性表元素 getVal
通过下标获取对应下标索引的值
- 功能:根据下标获取某个元素
- 参数:线性表指针
- 返回值:-1: 参数错误/为空, 正常返回获取的元素
/* 功能:根据下标获取某个元素 参数:线性表指针 返回值:-1: 参数错误/为空 正常返回获取的元素 */ data_t list_getVal(sqlist* list, int index) //获取ai的值 { //入口 参数检查 if(list == NULL) printf("list_getVal: list is NULL"); return list->data[index]; }
(2)定位线性表元素的下标 list_locate
获取某个元素在线性表内的下标索引,同时可通过下标判断某个元素是否在线性表内(返回 -1 表示该元素不在表内)
- 功能:获取某元素在线性表的下标
- 参数:para1: 线性表指针, para2: 需要定位的元素值
- 返回值:返回value在表中的下标, 不存在则返回-1
/* 功能:定位运算 参数:线性表指针, 需要定位的元素值 返回值:返回value在表中的下标, 不存在则返回-1 */ int list_locate(sqlist* list, data_t value) //确定元素value在表中的下标 { if(list == NULL) return -1; //比较value是否在 list内 for(int i = 0; i <= list->n; i++) { if(list->data[i] == value) return i; } return -1; }
(3)线性表插入元素(头插+尾插+任意位置)
1. 头插法插入数据 list_push_front
头插法插入数据在入口参数判断后:
①首先将所有数据往后移动1位
②接着将需要插入的数据存入线性表第一位
③记得线性表长度+1
- 功能:头插法插入元素
- 参数:para1:线性表指针 para2:待插入的元素
- 返回值:-1: 参数错误 0: 插入成功
头插法插入数据需要遍历移动整个线性表,因此插入数据的效率较低。/* 功能:头插法插入元素 参数:para1:线性表指针 para2:待插入的元素 返回值:-1: 参数错误 0: 插入成功 */ int list_push_front(sqlist* list, data_t value) { //入口参数检查 if(list == NULL || list->n >= N-1){ printf("push_front para error\n"); return -1; } //右移 list->n++; //线性长+1 for(int i = list->n; i > 0; i--) list->data[i] = list->data[i-1]; //插入新数据 list->data[0] = value; return 0; }
2. 尾插法插入数据 list_push_back
尾插法只需表长+1,在线性表尾插入元素即可:
- 功能:尾插法插入元素
- 参数:para1:线性表指针 para2:待插入的元素
- 返回值:-1: 参数错误 0: 插入成功
尾插法插入数据直接在表尾插入即可,因此效率较高。/* 功能:尾插法插入元素 参数:para1:线性表指针 para2:待插入的元素 返回值:-1: 参数错误 0: 插入成功 */ int list_push_back(sqlist* list, data_t value) { //入口参数检查 if(list == NULL || list->n >= N-1){ printf("push_back para error\n"); return -1; } //线性表尾插入数据 list->n++; list->data[list->n] = value; return 0; }
3. 任意位置插入数据 list_insert
在任意位置插入元素需要判断线性表是否为空或已满,插入位置是否合法,若插入位置大于表长,则在表尾插入,使线性表空间有序。
若在合法位置插入数据,需要将插入位置后续的数据往后移1位,空出目标位置插入新的数据,同时线性表长度将+1
- 功能:特定位置插入元素
- 参数:para1:线性表指针 para2:待插入的元素
- 返回值:-1:参数错误 0:插入成功
/*
功能:特定位置插入元素
参数:para1:线性表指针  para2:待插入的元素
返回值:-1:参数错误 0: 插入成功
*/
int list_insert(sqlist* list, int index, data_t value)
{
//入口参数检查
if(list == NULL){
printf("list_insert para error\n");
return -1;
}
//插入位置有误
if(index < 0 || list->n >= N-1){
printf("index index error\n");
return -1;
}
//插入位置大,放表尾
else if(index > list->n){
list->n++;
list->data[list->n] = value;
return 0;
}
else
{
list->n++; //表长+1
//右边数据后移 从后往前
for(int i = list->n; i > index; i--)
list->data[i] = list->data[i-1];
//插入数据
list->data[index] = value;
return 0;
}
}
(4)根据索引删除元素 list_delete
在删除元素之前先判断线性表是否为空,传入的删除下标位置是否合法,删除操作只需将目标位置后面的数据往前移1位即可。
- 功能:删除索引为index的元素
- 参数:para1:线性表指针, para2:待删除的下标位置
- 返回值:-1: 参数错误 0: 删除成功
/*
功能:删除索引为index的元素
参数:para1:线性表指针, para2:待删除的下标位置
返回值:-1: 参数错误 0: 删除成功
*/
int list_delete(sqlist* list, int index)
{
//入口参数检查: 表空 / 删除索引非法
if(list == NULL || list->n == -1 || index < 0 || index > list->n ){
printf("list_delete para error\n");
return -1;
}
//数据左移 覆盖即删除
for(int i = index; i < list->n; i++){
list->data[i] = list->data[i+1];
}
//线性表长度减1
list->n--;
return 0;
}
(5)线性表的合并 list_merge
线性表的合并即将list2中的元素合并至list1,如果list1中存在list2中的元素不并入,否则将list2的数据加入list1
- 功能:线性表合并, list2并入list1
- 参数:参数1:线性表1指针 参数2: 线性表2指针
- 返回值:–1: 参数错误 0: 合并成功
程序设计思路:通过遍历list2中的元素,判断list2中的每个元素是否在list1中出现过(list_locate)。若出现过则丢弃,若没有出现过,则尾插进list1(list_push_back)
/*
功能:线性表合并 list2并入list1
参数:参数1:线性表1指针 参数2: 线性表2指针
返回值:--1: 参数错误 0: 合并成功
*/
int list_merge(sqlist* list1, sqlist* list2)
{
//入口参数错误
if(list1 == NULL || list2 == NULL)
return -1;
for(int i = 0; i <= list2->n; i++)
{
int ret = list_locate(list1, list2->data[i]);
//找到list2中有元素不在list1,尾插数据
if(ret == -1){
list_push_back(list1, list2->data[i]);
}
}
return 0;
}
(5)线性表的去重 list_purge
线性表的去重即去除表内的重复元素
程序设计思路: 依次遍历a1…an元素ai,判断ai是否在[a0,ai-1]内出现过,若出现过则删除ai,重新比较ai,若没有出现过,则进行下一轮判断。
- 功能:删除线性表中重复元素
- 参数:线性表指针指针
- 返回值:-1: 参数错误 0: 删除成功
/*
功能:删除线性表中重复元素
参数:线性表指针
返回值:-1: 参数错误 0: 删除成功
*/
//依次取i [1,n],判断ai是否在[0,ai-1]内
//若不在,i++,若在,删除ai,重新比ai
int list_purge(sqlist* list)
{
//入口参数检查
if(list == NULL){
printf("list_purge: list error\n");
return -1;
}
//只有1个数
if(list->n == 0)
return 0;
int i = 1;
//开始遍历 i [1,n]的元素
while(i <= list->n)
{
int j = i-1;
//开始遍历 [0,ai-1]元素
while(j >= 0)
{
if(list->data[i] == list->data[j])
{
list_delete(list, i);
break;
}
else
j--;
}
//没找到
if(j < 0)
i++;
}
return 0;
}
附件:测试程序及源程序
线性表插入与删除测试程序:
//线性表插入与删除测试
void test_inert_delete()
{
sqlist* list = list_create();
if(list == NULL){
printf("list create error\n");
return;
}
//头插 尾插 中间插 并显示
list_push_back(list, 66);
list_push_back(list, 100);
list_push_back(list, 22);
list_push_front(list,12);
list_insert(list,3,76);
list_show(list);
//删除数据并显示
list_delete(list,2);
list_show(list);
//释放表
list_free(list);
}
线性表合并测试函数:
void test_merge()
{
//创建线性表1
sqlist* list1 = list_create();
list_push_back(list1,10);
list_push_back(list1,20);
list_push_back(list1,30);
//创建线性表2
sqlist* list2 = list_create();
list_push_back(list2,30);
list_push_back(list2,50);
//将表2合并至表1
list_merge(list1,list2);
list_show(list1);
list_free(list1);
list_free(list2);
}
测试删除重复元素:
void test_purge()
{
//创建线性表1
sqlist* list = list_create();
list_push_back(list,10);
list_push_back(list,10);
list_push_back(list,10);
list_push_back(list,10);
list_push_back(list,10);
list_push_back(list,10);
list_show(list);
list_purge(list);
list_show(list);
list_free(list);
}
sqlist.c:
#include "sqlist.h"
/********************函数实现*******************/
/*
功能:建立一个空的线性表
参数:void
返回值:线性表指针
*/
sqlist* list_create()
{
//申请内存空间
sqlist* list = (sqlist *)malloc(sizeof(sqlist));
if(list == NULL){
printf("list_create: malloc error\n");
return NULL;
}
//线性表成员变量赋值
list->n = -1;
return list;
}
/*
功能:清空线性表
参数:线性表指针
返回值:-1---参数错误 成功返回 0
*/
int list_clear(sqlist* list)
{
//入口 参数检查
if(list == NULL){
printf("list_clear: list is NULL");
return -1;
}
//清空数组数据
int len = list_length(list);
memset(list->data, '\0', sizeof(data_t) * len);
list->n = -1;
return 0;
}
/*
功能:判断线性表是否为空
参数:线性表指针
返回值:空:返回1 非空: 返回0
*/
int list_empty(sqlist* list)
{
if(list->n == -1)
return 1;
else
return 0;
}
/*
功能:求线性表长度
参数:线性表指针
返回值:-1---参数错误 >0 表长
*/
int list_length(sqlist* list)
{
//入口 参数检查
if(list == NULL){
printf("list_length: list is NULL");
return -1;
}
//返回线性表长度
return list->n + 1;
}
/*
功能:显示线性表
参数:线性表指针
返回值:void
*/
void list_show(sqlist* list)
{
if(list->n == -1)
printf("list is NULL");
if(list != NULL)
{
for(int i = 0; i <= list->n; i++)
printf("%d ",list->data[i]);
printf("\n");
}
else
printf("list is NULL\n");
}
/*
功能:释放线性表内存表空间
参数:线性表指针
返回值:-1: 参数错误/为空 0: 释放成
*/
int list_free(sqlist* list)
{
if(list == NULL)
return -1;
free(list);
list = NULL;
return 0;
}
/*
功能:根据下标获取某个元素
参数:线性表指针
返回值:-1: 参数错误/为空 正常返回获取的元素
*/
data_t list_getVal(sqlist* list, int index) //获取ai的值
{
//入口 参数检查
if(list == NULL)
printf("list_getVal: list is NULL");
return list->data[index];
}
/*
功能:定位运算
参数:线性表指针, 需要定位的元素值
返回值:返回value在表中的下标, 不存在则返回-1
*/
int list_locate(sqlist* list, data_t value) //确定元素value在表中的下标
{
if(list == NULL)
return -1;
//比较value是否在 list内
for(int i = 0; i <= list->n; i++)
{
if(list->data[i] == value)
return i;
}
return -1;
}
/*
功能:尾插法插入元素
参数:para1:线性表指针 para2:待插入的元素
返回值:-1: 参数错误 0: 插入成功
*/
int list_push_back(sqlist* list, data_t value)
{
//入口参数检查
if(list == NULL || list->n >= N-1){
printf("push_back para error\n");
return -1;
}
//线性表尾插入数据
list->n++;
list->data[list->n] = value;
return 0;
}
/*
功能:头插法插入元素
参数:para1:线性表指针 para2:待插入的元素
返回值:-1: 参数错误 0: 插入成功
*/
int list_push_front(sqlist* list, data_t value)
{
//入口参数检查
if(list == NULL || list->n >= N-1){
printf("push_front para error\n");
return -1;
}
//右移
list->n++; //线性长+1
for(int i = list->n; i > 0; i--)
list->data[i] = list->data[i-1];
//插入新数据
list->data[0] = value;
return 0;
}
/*
功能:特定位置插入元素
参数:para1:线性表指针 para2:待插入的元素
返回值:-1:参数错误 0: 插入成功
*/
int list_insert(sqlist* list, int index, data_t value)
{
//入口参数检查
if(list == NULL || list->n >= N-1){
printf("list_insert para error\n");
return -1;
}
//线性表满了
if(index < 0 || index > N){
printf("index index error\n");
return -1;
}
//插入位置大,放表尾
else if(index > list->n){
list->n++;
list->data[list->n] = value;
return 0;
}
else
{
list->n++; //表长+1
//右边数据后移 从后往前
for(int i = list->n; i > index; i--)
list->data[i] = list->data[i-1];
//插入数据
list->data[index] = value;
return 0;
}
}
/*
功能:删除索引为index的元素
参数:para1:线性表指针, para2:待删除的下标位置
返回值:-1: 参数错误 0: 删除成功
*/
int list_delete(sqlist* list, int index)
{
//入口参数检查: 表空 / 删除索引非法
if(list == NULL || list->n == -1 || index < 0 || index > list->n ){
printf("list_delete para error\n");
return -1;
}
//数据左移 覆盖即删除
for(int i = index; i < list->n; i++){
list->data[i] = list->data[i+1];
}
//线性表长度减1
list->n--;
return 0;
}
/*
功能:线性表合并 list2并入list1
参数:参数1:线性表1指针 参数2: 线性表2指针
返回值:--1: 参数错误 0: 合并成功
*/
int list_merge(sqlist* list1, sqlist* list2)
{
//入口参数错误
if(list1 == NULL || list2 == NULL)
return -1;
for(int i = 0; i <= list2->n; i++)
{
int ret = list_locate(list1, list2->data[i]);
//找到list2中有元素不在list1,尾插数据
if(ret == -1){
list_push_back(list1, list2->data[i]);
}
}
return 0;
}
/*
功能:删除线性表中重复元素
参数:线性表指针
返回值:-1: 参数错误 0: 删除成功
*/
//依次取i [1,n],判断ai是否在[0,ai-1]内
//若不在,i++,若在,删除ai,重新比ai
int list_purge(sqlist* list)
{
//入口参数检查
if(list == NULL){
printf("list_purge: list error\n");
return -1;
}
//只有1个数
if(list->n == 0)
return 0;
int i = 1;
//开始遍历 i [1,n]的元素
while(i <= list->n)
{
int j = i-1;
//开始遍历 [0,ai-1]元素
while(j >= 0)
{
if(list->data[i] == list->data[j])
{
list_delete(list, i);
break;
}
else
j--;
}
//没找到
if(j < 0)
i++;
}
return 0;
}
sqlist.h
#ifndef __SQLIST__H
#define __SQLIST__H
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
//变量与顺序表结构定义
#define N 100
typedef int data_t;
typedef struct{
data_t data[N]; //表的存储空间
int n; //线性表中最后一个元素下标
}sqlist;
/*****************函数声明*******************/
/*************顺序表的基本属性***************/
sqlist* list_create(int n); //建立一个空表
int list_clear(sqlist* list); //清空表
int list_empty(sqlist* list); //判断表是否为空
int list_length(sqlist* list); //求线性表长度
void list_show(sqlist* list); //显示线性表
int list_free(sqlist* list); //释放线性表空间
/*************顺序表的相关运算***************/
data_t list_getVal(sqlist* list, int pos); //根据索引获取值
int list_locate(sqlist* list, data_t value); //确定元素value的索引值
int list_push_back(sqlist* list, data_t value); //尾插法
int list_push_front(sqlist* list, data_t value); //头插法
int list_insert(sqlist* list, int pos, data_t value); //在特定位置插入元素
int list_delete(sqlist* list, int index); //删除一个元素
int list_merge(sqlist* list1, sqlist* list2); //线性表合并 list2并入list1
int list_purge(sqlist* list1); //删除线性表中重复元素
#endif