前言:
在上一篇文章中,我们已经对数据结构有了一定了解,我们可以通过优化空间复杂度或者时间复杂度从而提高我们程序运行或存储速率。至此我们就知道了数据结构的重要性,所以今天我们将要了解和学习一种实用的数据结构——线性表。
一、线性表概述:
线性表(linear list)是数据结构的一种,一个线性表是n个具有相同特性的数据元素的有限序列,是最基本、最简单也最常用的一种数据结构。
线性表中的数据元素之间的关系是一对一的关系,即除了第一个和最后一个数据元素之外,其他数据元素都是首尾相连的(注意,这句话只适用大部分线性表,而不是全部。比如,循环链表逻辑层次上也是线性表,但存储层次上属于链式存储,是把最后一个数据元素的尾指针指向了首位节点)
线性表在逻辑上是线性结构,但是在物理结构并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。 常见的线性表:顺序表、链表、栈、队列、字符串等等。
二、顺序表:
了解完线性表后,就要进入我们今天的主题,我们今天最主要学的的是线性表中的顺序表。
概念和结构:
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储,并在数组上完成数据的增删查改。其本质就是数组,但与数组不同的是:顺序表在其数组本质的基础上,还要求数据连续存储,不能跳跃或间隔存储。
顺序表一般可分为两类:
静态顺序表:使用一定长度的数组存储元素。但是静态顺序表存在很明显的缺陷,即大小固定,数据少了就会造成空间浪费,数据多了空间不够导致不能存储。
#define N 10
typedef int SLDataType;
typedef struct SeqList
{SLDataType arr[N];int size;
}SeqList;
动态顺序表:使用动态开辟的数组存储元素。动态顺序表很好弥补了静态顺序表的弊端,使空间申请变得灵活,我们可根据情况进行扩容操作从而申请到合适大小的空间。
typedef int SLDataType;
typedef struct SeqList
{SLDataType* a;int size;inte capacity;
}SeqList;
2、顺序表的实现:
现在开始,我们开始实现顺序:
①初始化顺序表:
void SeqListInit(SeqList* ps)
{assert(ps);//防止传入空指针;ps->a = malloc(sizeof(SLDateType) * INIT_CAPACITY);if (ps->a == NULL){perror("malloc fail");}ps->size = 0;ps->capacity = INIT_CAPACITY;}
②销毁顺序表:
void SeqListDestroy(SeqList* ps)
{assert(ps);//防止传入空指针;free(ps->a);ps->a = NULL;ps->capacity = ps->size=0;
}
③顺序表尾插:
尾插即在最后一个元素下一个位置上插入元素,也就是size的位置上,插入之后,总数据+1也就是size++。
void SeqListPushBack(SeqList* ps, SLDateType x)
{assert(ps);//检查是否需要扩容if (ps->size == ps->capacity){SLDateType* tmp = (SLDateType)realloc(ps->a, sizeof(SLDateType) * ps->capacity + 5);if (tmp == NULL){perror("realloc fail");return;}ps->a = tmp;ps->capacity += 5;}ps->a[ps->size++] = x;
}
④顺序表尾删:
尾删就是把最后一个元素删除,所以size不能为0,删除之后总数据减一,size--。
void SeqListPopBack(SeqList* ps)
{assert(ps);assert(ps->size > 0);ps->size--;}
⑤顺序表头插:
头插也就是在第一个元素前面插入第一个元素,因为我们使用数组实现的顺序表所以头插需要挪动元素。
插入之后,总数据+1也就是size++。
void SeqListPushFront(SeqList* ps, SLDateType x)
{assert(ps);if (ps->size == ps->capacity){SLDateType* tmp = (SLDateType)realloc(ps->a, sizeof(SLDateType) * ps->capacity + 5);if (tmp == NULL){perror("realloc fail");return;}ps->a = tmp;ps->capacity += 5;}for (int i = ps->size; i > 0; i--){ps->a[i] = ps->a[i - 1];}ps->a[0] = x;ps->size++;}
⑥顺序表的头删除:
与头插类似,同样需要挪动元素,只不过挪动方向变了,头插是往后挪,头删是往前挪,删除之后总数据减一,size--。
void SeqListPopFront(SeqList* ps)
{assert(ps);int count = 1;for (count = 1; count <= ps->size; count++){ps->a[count - 1] = ps->a[count];}ps->size--;
}
⑦打印顺序表:
void SeqListPrint(SeqList* ps)
{if (ps->size == 0){printf("顺序表为空");return;}for (int i = 0; i < ps->size; i++){printf("%d ", ps->a[i]);}printf("\n");
}
⑧顺序表中查找:
int SeqListFind(SeqList* ps, SLDateType x)
{assert(ps);for (int i = 0; i < ps->size; i++){if (ps->a[i] == x){return i;}return -1;}
}
⑨在指定下标插入:
插入之后,总数据+1也就是size++。
void SeqListInsert(SeqList* ps, int pos, SLDateType x)
{assert(ps);assert(pos >= 0 && pos <= ps->size);if (ps->size == ps->capacity){SLDateType* tmp = (SLDateType)realloc(ps->a, sizeof(SLDateType) * ps->capacity + 5);if (tmp == NULL){perror("realloc fail");return;}ps->a = tmp;ps->capacity += 5;}for (int i = ps->size; i > pos;i--){ps->a[i] = ps->a[i - 1];}ps->a[pos] = x;ps->size++;
}
⑩删除指定下标位置元素:
删除之后总数据减一,size--。
void SeqListErase(SeqList* ps, int pos)
{assert(ps);assert(pos >= 0 && pos <= ps->size);for (int i = pos; i < ps->size-1; i++){ps->a[i] = ps->a[i + 1];}ps->size--;}
总结:
顺序表相对来说比较简单,很重要一点就是在执行操作时,要思考是否要进行断言操作,避免使用空指针。文章到此就结束啦。