目录
- 线性表
- 顺序表
- 练习
线性表(Linear list)是n个具有相同特性的数据元素的有限序列。线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串。。。
线性表在逻辑上时线性结构,是连续的一条直线。但在物理结构上不一定是连续的,存储时,通常以数组和链式结构的形式存储
2. 顺序表
2.1 概念和结构
顺序表是一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改
顺序表一般可以分为:
-
静态顺序表:使用定长数组存储元素
-
使用动态开辟的数组
2.2 接口实现
静态顺序表只适用于确定知道存多少数据的场景.静态顺序表的定长数组导致N定大了,空间开多了浪费,开少了不够用.所以现实中基本都是使用动态顺序表,根据需要动态的分配空间大小,所以下面我们实现动态顺序表
头文件
#pragma once
#include <stdio.h>
顺序表的静态存储
//#define N 7
//typedef int SLDataType;
//
//typedef struct _SeqList
//{
// SLDataType array[N]; //定长数组
// size_t size; //有效数据的个数
//}SeqList;//顺序表的动态存储
typedef int SLDataType;typedef struct _SeqList
{SLDataType *array; //动态开辟的数组size_t size; //有效数据的个数size_t capacity; //容量大小
}SeqList;//接口
//初始化
void SLInit(SeqList* psl);//增加
//头插
void SLPushFront(SeqList* psl, SLDataType data);
//尾插
void SLPushBack(SeqList* psl, SLDataType data);
//中间插
void SLInsert(SeqList* psl, int pos, SLDataType data);//打印
void SLPrint(SeqList* psl);
//头删
void SLPopFront(SeqList* psl);
//尾删
void SLPopBack(SeqList* psl);
//中间删
void SLEarse(SeqList* psl, int pos);//查询
int SLFind(SeqList* psl, SLDataType data);
// 修改
void SLModify(SeqList* psl, int pos, SLDataType data);
//销毁
void SLDestory(SeqList* psl);
实现文件
#include "SeqList.h"
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>void SLInit(SeqList* psl)
{assert(psl);psl->array = (SLDataType*)malloc(sizeof(SLDataType) * 4);if (psl->array == NULL){perror("malloc fail");return;}psl->capacity = 4;psl->size = 0;
}//检查容量
void CheckCapc(SeqList* psl)
{assert(psl);if (psl->size == psl->capacity){SLDataType* temp = (SLDataType*)realloc(psl->array, sizeof(SLDataType) * psl->capacity * 2);if (temp == NULL){perror("realloc fail");return;}psl->array = temp;psl->capacity = psl->capacity * 2;}}void SLPushFront(SeqList* psl, SLDataType data)
{assert(psl);/*CheckCapc(psl);int end = psl->size - 1;while (end >= 0){psl->array[end + 1] = psl->array[end];end--;}psl->array[0] = data;psl->size++;*///调用中间插入SLInsert(psl, 0, data);
}void SLPushBack(SeqList* psl, SLDataType data)
{assert(psl);/*CheckCapc(psl);psl->array[psl->size] = data;psl->size++;*///调用中间插入SLInsert(psl, psl->size, data);
}void SLInsert(SeqList* psl, int pos, SLDataType data)
{assert(psl);assert(pos >= 0 && pos <= psl->size);CheckCapc(psl);int end = psl->size - 1;while (end >= pos){psl->array[end + 1] = psl->array[end];end--;}psl->array[pos] = data;psl->size++;
}void SLPrint(SeqList* psl)
{assert(psl);for (int i = 0; i < psl->size; i++){printf("%d ", psl->array[i]);}printf("\r\n");
}void SLPopFront(SeqList* psl)
{assert(psl);assert(psl->size > 0);/*int start = 0;while (start < psl->size - 1){psl->array[start] = psl->array[start + 1];start++;}psl->size--;*/SLEarse(psl, 0);
}void SLPopBack(SeqList* psl)
{assert(psl);assert(psl->size > 0);psl->size--;
}void SLEarse(SeqList* psl, int pos)
{assert(psl);assert(psl->size > 0);int start = pos + 1;while (start < psl->size){psl->array[start - 1] = psl->array[start];start++;}psl->size--;
}int SLFind(SeqList* psl, SLDataType data)
{assert(psl);int i = 0;for (; i < psl->size; i++){if (psl->array[i] == data)return i;}return -1;
}void SLModify(SeqList* psl, int pos, SLDataType data)
{assert(psl);assert(pos >= 0 && pos <= psl->size);psl->array[pos] = data;
}void SLDestory(SeqList* psl)
{assert(psl);if (psl->array != NULL){free(psl->array);psl->array = NULL;}psl->capacity = 0;psl->size = 0;psl = NULL;
}
主文件
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "SeqList.h"int main()
{SeqList array;SLInit(&array);//增加SLPushBack(&array, 1);SLPushBack(&array, 2);SLPushBack(&array, 3);SLPushBack(&array, 4);SLPushBack(&array, 5);SLPushFront(&array, 6);SLPrint(&array);//删除SLPopBack(&array);SLPrint(&array);SLPopFront(&array);SLPrint(&array);SLInsert(&array, 4, 4);SLPrint(&array);//删除指定元素SLEarse(&array, 2);SLPrint(&array);SLInsert(&array, 4, 8);SLPrint(&array);int pos = SLFind(&array, 8);if (pos != -1){SLModify(&array, pos, 10);}SLPrint(&array);SLDestory(&array);return 0;
}
传入顺序表指针的函数都需要检查一下指针是否为空,用断言直接报错的好处在于运行的时候哪里出问题报错提示很明显
菜单界面在调试阶段最好别加,影响调试效率,菜单界面也不是必须的
菜单项在添加数据的时候,如何判断输入数据结束了。用-1或某个值也可能遇到用户刚好需要存入-1的情况。这时可以先输入插入数据的数量,再逐个输入数据
当scanf接收输入失败的时候会卡住下次输入,如果不清空缓冲区,可能会无限读入,可以通过判断scanf返回值看是否输入成功
3. 练习
移除元素: https://leetcode.cn/problems/remove-element/
第一种:
暴力求解,遍历数组,遇到和值一样的,将后面的往前覆盖,继续查找
时间复杂度:O(N2)
空间复杂度: O(1)
第二种:
新建一个数组,遇到值和给定值不一样的放入新数组里
时间复杂度:O(N)
空间复杂度: 开辟了新数组, O(N)
第三种:
和第二种思路相似,但不开辟新数组,在原数组上修改。用两个下标,一个des,一个src,遇到和给定值不一样的,将des处值改为src的值,两个下标都增加1,遇到一样的,只增加src下标。当src遍历完数组,返回长度
int removeElement(int* nums, int numsSize, int val) {int sign1 = 0;int sign2 = 0;for(int i = 0; i < numsSize; i++){if(nums[sign1] != val){nums[sign2] = nums[sign1];sign1++;sign2++;}else{sign1++;}}return sign2;
}