线性表
:线性表是具有相同数据类型的n个(n>=0)个数据元素的有限序列,其中n为表长,当n==0是线性表为空表,若是使用 L
命名为线性表,其结构为:
L=(a1,a2,a3,a4,....,an);
其中a1为表头元素
,an为 表尾元素
除第一个元素外,每一个元素 有且仅有一个直接前驱
,除最后一个元素外,每一个元素 有且仅有一个直接后继
,这就是线性表的 逻辑特性。
注意:线性表仅仅是一种逻辑结构,表示元素之间一对一的相邻关系。后面所要讲的顺序表
和链表
值得是存储结构,切勿将其混淆。
线性表的顺序储存结构
:指的是用一段 地址连续的存储单元一次存储线性表的数据元素
让我们先来看看如何定义顺序表
#define MAXSIZE 20
typedef int Elemtype;//具体类型由实际情况而定
typedef struct {
Elemtype data[MAXSIZE];
int length;//线性表当前表长
}Sqlist;
注意:线性表中的下标一般是从0开始的,但是我们平时说的位序一般时从1开始的
不难看出顺序存储的核心即使其中所定义的data数组,我这里采取的时 静态分配 的方式,同样也可以用 动态分配也就是malloc那种形式的分配内存,那样更加灵活
一下为动态内存分配的方式
typedef struct{
Elemtype* data;
int length;
}Seqlist;
其初始动态分配语句为:
L.data=(Elemtype*)malloc(sizeof(Elemtype)*Maxsize);
值得注意的时动态内存分配并不是链式储存,同样还是顺序存储结构,物理顺序依然和逻辑顺序保持一致,只是分配空间大小可以在运行时动态决定
主要特点:
1.随机访问
2.由于逻辑上相邻的元素物理上也是相邻,所以增加和删除要移动大量元素
3.储存密度高
王道书上要求的操作有:
首先定义一下函数状态码等
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
typedef int Status;//定义函数类型,其值是返回的是函数状态码,如ok等
顺序表的初始化:
//链表的初始化
Status Initlist(Sqlist* L) {
L->length = 0;
return OK;
}
顺序表求表长:
//输出顺序线性表长
int ListLength(Sqlist L) {
return L.length;
}
顺序表按值查找:
//按值查找操作,在线性表L中找到指定元素并且返回其位置
int LocateElem(Sqlist L, Elemtype e) {
if (L.length == 0) {
return -1;
}
int i = L.length;
int j;
for (j = 0; j < i; ++j) {
if (L.data[j] == e) {
return j + 1;
}
}
return -1;
}
顺序表按位查找:
//找出数组中第i个元素的位置,注意i是指位置,所以下标从0开始要向后+1
//值用e返回
int GetElem(Sqlist L,int i, Elemtype*e) {
if (L.length == 0 || i<1 || i>L.length) {
return ERROR;
}
*e = L.data[i - 1];
return OK;
}
顺序表插入(熟练掌握):
//插入操作,在表中的i位置插入指定元素e
Status ListInsert(Sqlist* L, int i, Elemtype e) {
if (L->length== MAXSIZE) {
return ERROR;
}
if (i<1 || i>(L->length + 1)) {
//如果插入元素比第一个位置还小或者比最后一个元素的后一位还大时不成立
return ERROR;
}
if (i <= L->length) {
//要先把原有所有元素往后移动一位
for (int j =L->length-1; j >= i-1; --j) {
L->data[j+ 1] = L->data[j];
}
}
L->data[i - 1] = e;
L->length++;
return OK;
}
顺序表删除(熟练掌握):
//线性表的删除操作。删除表L中的第i个元素并且用e返回其值
Status ListDelete(Sqlist* L, int i, Elemtype* e) {
if (L->length == 0 || i < 1 || i>L->length||e == NULL) {
return ERROR;
}
*e = L->data[i - 1];
if (i < L->length) {
//如果不删除最后一个位置则要操作,否则不用操作
for (int j = i; j <L->length; ++j) {
//每个元素从删除位置向前移
L->data[i - 1] = L->data[i];
}
}
L->length--;
return OK;
}
顺序表中数据依次输出:
//输出操做,按前后顺序依次输出线性表L中得到所有元素值
void PrintList(Sqlist L){
int i;
for (i = 0; i < L.length; ++i) {
printf("%d", L.data[i]);
printf("\n");
}
return OK;
}
顺序表中判空:
//判断链表是否为空
Status IsEmpty(Sqlist L) {
if (L.length == 0) {
return TRUE;
}
else
{
return FALSE;
}
}
清空:
//清空链表
Status ClearList(Sqlist* L) {
L->length = 0;
return OK;
}
两个顺序表的合并
//线性表的合并
void unionL(Sqlist* L, Sqlist L1) {
int lengthl = ListLength(*L);
int lengthl1 = ListLength(L1);
Elemtype e;
if (lengthl == MAXSIZE) {
//说明已满不能插入
return ERROR;
}
for (int i = 1; i <= lengthl1; i++) {
//遍历要加入的表l1
GetElem(L1, i, &e);//找到相对应元素放入e
if (LocateElem(*L, e) == -1) {
//说明没有该元素
ListInsert(L, ++i, e);//插入
}
}
}
以上即为王道上的基本操作,具体题目还要随机应变
王道课后编程题晚点出。。
这里就插入和删除和修改操作进行评价
最好时间复杂度:在表尾插入元素不用向后移,为最好,复杂度
O(1)
; 最坏时间复杂度:在表头,为大O(n)
;
平均时间复杂度:如果是在第一个点插入,n个元素向后移,若是在第二点插入为n-1个元素向后移,假设所有可能插入位置概率相同即为k=1/(n+1)
也就可以轻易退出公式是为
(1/(n+1))*(n-i+1)
其中i从1到n+1,所以平均次数为n/2
,因此时间复杂度为大O(n)
最好时间复杂度:在表尾插入元素不用向前移,为最好,复杂度
O(1)
; 最坏时间复杂度:在表头,后面都要前移,为大O(n)
;
平均时间复杂度:跟上面的最坏求法一样,就可以得出时间复杂度为大O(n)
;
如果是通过下标查找当然是可以直接找到,按值则要遍历
最好时间复杂度:在表头查找元素,为最好,复杂度
O(1)
; 最坏时间复杂度:在表尾,所有元素遍历一遍,为大O(n)
;
平均时间复杂度:跟上面的最坏求法一样,就可以得出时间复杂度为大O(n)
;
总结:
无论是顺序查找(按值)还是删除插入,最好最坏和平均时间复杂度都一样
习题易错点:
1.顺序存储支持随机存取
这里还是说明一下…
随机存取就是直接存取,可以通过下标直接访问的那种数据结构,与存储位置无关,例如数组。非随机存取
就是顺序存取了,不能通过下标访问了,只能按照存储顺序存取,与存储位置有关,例如链表。
好像没啥好错的地方了,就到这里吧
明天肝链表冲冲冲