一、 数据结构的基本概念
1.以下基本概念截图来自大话数据结构
数据项是数据不可分割的最小数据单位
2.关于逻辑结构和物理结构
逻辑结构:数据与数据关系的抽象关系,与物理地址无关
物理结构(存储结构):数据与数据之间,它在存储器中的存放方式
逻辑结构的分类:集合、线性、树形结构、图形结构、
物理结构的分类:顺序存储结构、链式存储结构
3.时间复杂度、空间复杂度
时间复杂度:算法中基本操作执行次数和问题规模n之间的关系,一般记为O(f(n))
例题:
//在计算时间复杂度的时候,记住 只要最高项,不要系数
//1 O(n)
for(int i=1; i<=n; i++)
{
tmp += arr[i];
}
//2. O(n^2)
for(int i=1; i<=n; i++)
{
for(int j=1; j<=n; j++)
{
arr[i][j] = 1;//基本操作语句
}
}
//3 O(1)
{
++x;
s=0;
}
//4 O(n)
for(int i=1; i<=n; i++)
{
++x;
s+=x;
}
//5 O(n^2)
for(int i=2; i<=n; i++)
{
for(int j=2; j<=i-1; ++j)
{
++x;
arr[i][j] = x;//基本执行语句
}
}
空间复杂度:算法中需要额外辅助空间和问题规模n之间的关系,一般记为O(f(n))
例题:(递归)
//递归中开辟的空间不会随着函数调用而消失
//递归里面的空间复杂度 O(n)
int Fun(int n) //调用了n次函数,开辟了n次空间
{
if(n<=1)
return n;
else
return Fun(n-1)+1;
}
4.关于线性表的概念
线性表:有且只有一个开始结点,有且只有一个结束结点,并且除了开始结点外,其余结点都有直接前驱,并且除了结束结点外,其余结点都有直接后继,表示为:{a1,a2,a3,…an-1,an}
简而言之,逻辑关系是一对一的,例如:线性表,栈和队列,字符串,数组…
线性表按照其存储方式有两种
顺序表:数据结点间,物理相邻,逻辑也相邻
链表:数据结点间,逻辑相邻,物理不一定相邻
二、顺序表
将逻辑上相邻的数据元素,存储在物理上也相邻的存储单元中
1.用代码的方式实现顺序表
.h头文件定义结构体以及函数
#pragma once
#define LISTINCREMENT 100 //初始大小
#define LIST_INIT_SIZE 10 //不需要
//可扩容顺序表结构体设计
typedef int ELEM_TYPE;
typedef struct Sqlist {
ELEM_TYPE* elem; //存储空间基址(用来接受malloc返回在堆上申请的连续空间块开辟地址)
int length; //当前有效长度
int listsize ; //当前总空间大小(以格子数为单位)
}Sqlist,*PSqlist;
//关于数据结构的操作,无外乎就是增删改查
//初始化
void Init_Sqlist(struct Sqlist* sq);
//头插
bool Insert_head(struct Sqlist* sq, ELEM_TYPE val);
//尾插
bool Insert_tail(struct Sqlist* sq, ELEM_TYPE val);
//换位置插
bool Insert_pos(struct Sqlist* sq, int pos, ELEM_TYPE val);
//头删
bool Del_head(struct Sqlist* sq);
//尾删
bool Del_tail(struct Sqlist* sq);
//按位置删
bool Del_pos(struct Sqlist* sq, int pos);
//按值删(找出这个值在顺序表中第一次出现的位置,然后删除)
bool Del_val(struct Sqlist* sq, ELEM_TYPE val);
//判空
bool Isempty(struct Sqlist* sq);
//判满
bool Isfull(struct Sqlist* sq);
//扩容函数
void Inc(struct Sqlist* sq);
//查找(找这个值在顺序表中,第一次出现的位置)
int Search(struct Sqlist* sq, ELEM_TYPE val);
//清空
void Clear(struct Sqlist* sq);
//销毁(将数据空间都释放掉)
void Destory(struct Sqlist* sq);
//打印
void Show(struct Sqlist* sq);
.cpp文件里面实现函数
#include<stdio.h>
#include"Sqlist.h"
#include<stdlib.h>
#include <cassert>
//初始化
void Init_Sqlist(struct Sqlist* sq) {
sq->elem = (ELEM_TYPE*)malloc(LISTINCREMENT * sizeof(int));
assert(sq->elem != NULL);
sq->length = 0;
sq->listsize = LISTINCREMENT;
}
//头插
bool Insert_head(struct Sqlist* sq, ELEM_TYPE val) {
assert(sq != NULL);
if (Isfull(sq)) {
Inc(sq);
}
else {
for (int i = sq->length - 1; i >=0; i--) {
sq->elem[i + 1] = sq->elem[i];
}
sq->elem[0] = val;
sq->length++;
}
return true;
}
//尾插
bool Insert_tail(struct Sqlist* sq, ELEM_TYPE val){
if (Isfull(sq)) {
Inc(sq);
}
else {
sq->elem[sq->length] = val;
sq->length++;
}
return true;
}
//按位置插
bool Insert_pos(struct Sqlist* sq, int pos, ELEM_TYPE val) {
if (Isfull(sq)) {
Inc(sq);
}
else {
for (int i = sq->length - 1; i >= pos; i--) {
sq->elem[i+1] = sq->elem[i];
}
sq->elem[pos] = val;
sq->length++;
}
return true;
}
//头删
bool Del_head(struct Sqlist* sq) {
assert(sq != NULL);
if (Isempty(sq)) {
return false;
}
else {
for (int i = 0; i < sq->length - 1; i++) {
sq->elem[i] = sq->elem[i + 1];
}
sq->length--;
}
return true;
}
//尾删
bool Del_tail(struct Sqlist* sq) {
//1.安全性处理(判断sq指针不指向空)
assert(sq != NULL);
//2.判空(判断sq指向的是否是一个空的顺序表)
assert(sq->elem != NULL);
//3.删除
if (Isempty(sq)) {
return false;
}
else {
sq->length--;
}
return true;
}
//按位置删
bool Del_pos(struct Sqlist* sq, int pos) {
//1.安全性处理(判断sq指针不指向空)
assert(sq != NULL);
//2.判空(判断sq指向的是否是一个空的顺序表)
assert(sq->elem != NULL);
//3.pos的合法性,pos默认为0时,为头删
assert(pos >= 0 && pos < sq->length);
if (Isempty(sq)) {
return false;
}
else {
for (int i = pos; i < sq->length - 1; i++) {
sq->elem[i] = sq->elem[i + 1];
}
sq->length--;
}
return true;
}
//按值删(找出这个值在顺序表中第一次出现的位置,然后删除)
bool Del_val(struct Sqlist* sq, ELEM_TYPE val) {
assert(sq != NULL);
//直接调用其它函数
int temp = Search(sq, val);
if (temp == -1) {
return false;
}
else {
return Del_pos(sq, temp);
}
}
//判空
bool Isempty(struct Sqlist* sq) {
assert(sq != NULL);
if (sq->length == 0) {
return true;
}
return false;
}
//判满
bool Isfull(struct Sqlist* sq) {
assert(sq != NULL);
if (sq->length == sq->listsize) {
return true;
}
return false;
}
//扩容函数 按2倍扩容
void Inc(struct Sqlist* sq) {
sq->elem=(ELEM_TYPE*)realloc(sq->elem,(sq->listsize * sizeof(int))*2);
assert(sq->elem != NULL);
sq->listsize *= 2;
}
//查找(找这个值在顺序表中,第一次出现的位置)
int Search(struct Sqlist* sq, ELEM_TYPE val) {
for (int i = 0; i < sq->length - 1; i++) {
if (sq->elem[i] == val) {
return i;
}
}
return -1;
}
//清空
void Clear(struct Sqlist* sq) {
assert(sq != NULL);
sq->length = 0;
}
//销毁(将数据空间都释放掉)
void Destory(struct Sqlist* sq) {
free(sq->elem);
sq->length = sq->listsize = 0;
}
//打印
void Show(struct Sqlist* sq) {
for (int i = 0; i < sq->length; i++) {
printf("%d", sq->elem[i]);
}
printf("\n");
}
.cpp在main函数里面测试代码
#include<stdio.h>
#include"Sqlist.h"
#include<stdlib.h>
#include <cassert>
int main() {
struct Sqlist head;
Init_Sqlist(&head);
for (int i = 0; i < 100; i++) {
Insert_pos(&head, i, i + 1);
}
Show(&head);
return 0;
}
2.总结
顺序表的特点:
- 顺序表的操作需要挪动元素,插入的话,需要向后挪动,删除的话,需要向前覆盖,时间复杂度为O(n),效率不高
但是,如果在尾部操作,则不需要挪动元素,效率极高,为O(1) - 顺序表的使用场景:
适合的场景:
2.1支持直接访问
2.2插入和删除操作,主要集中在尾部
不适合的场景:
在头部中间位置,插入和删除较多,频繁的扩容,扩容代价高