知识总览
顺序表
顺序表—用顺序存储的方式实现线性表
顺序存储。把逻辑上相邻的元素存储在物理位置上也相邻的存储单元中,元素之间的关系由存储单元的邻接关系来体现。
逻辑结构
存放位置
顺序表的特点
- 利用数据结构和存储位置表示线性表中相邻数据元素之间的前后关系,即线性表的逻辑结构与存储结构一致
- 访问线性表时,可以快速地计算出任何一个数据元素的存储地址,因此可以粗略地认为,访问每个元素所花时间相等
顺序表的特点:
- 随机访问。在O(1)时间内找到第i个元素
- 存储密度高,每个节点只存储数据元素
- 拓展容量不方便(即便用动态分配,拓展长度的时间复杂度也比较高)
- 插入、删除操作不方便,需移动大量元素
顺序表的操作
顺序表的实现–静态分配
#include <iostream>
#define MaxSize 10 //定义最大长度
using namespace std;
typedef struct{
int data[MaxSize]; //用静态的“数组”存放数据元素
int length; //顺序表的当前长度
}SqList; //顺序表的类型定义
void InitList(SqList &L)
{
for(int i=0;i<MaxSize;i++)
L.data[i] = 0;
L.length=0;
}
int main()
{
SqList L;
InitList(L);
for(int i=0;i<MaxSize;i++)
printf("%d\n",L.data[i]);
return 0;
}
data[MaxSize];
给各个数据元素分配连续的内存空间,大小为MaxSize*sizeof(int)
Q:如果“数组”存满了怎么办?
A:存储空间是静态的,表长确定后就不能更改了。
顺序表的实现–动态分配
#include<iostream>
#include<stdlib.h>
#define InitSize 10 //顺序表的初始长度
using namespace std;
typedef struct{
int *data; //指示动态分配数组的指针
int MaxSize; //顺序表的最大容量
int length; //顺序表的当前长度
}SqList; //顺序表的类型定义
void InitList(SqList &L) //申请内存
{
L.data = (int *)malloc(InitSize*sizeof(int));//强制转换成与 data相同
L.length = 0;
L.MaxSize = InitSize;
}
void IncreaseSize(SeqList &L , int len) //增加动态数组的长度
{
int *p = L.data;
L.data = (int *)malloc((L.MaxSize+len)*sizeof(int));
for(int i=0;i<L.length;i++){
L.data[i] = p[i]; //把原来的数据复制到新区域里
}
L.MaxSize = L.MaxSize + len;
free(p);
}
int main()
{
SqList L;
InitList(L);
IncreaseIsze(L , 5);
return 0;
}
注意点:
-
C语言通过malloc和free函数来动态申请和释放内存空间
而malloc返回一个指针,需要强制转型为你定义的数据元素类型指针
L.data = **(int )**malloc(InitSizesizeof(int))
强制转型:使用malloc函数申请一片内存空间时,要求强制转换为与数据相同的数据类型。如果指针是 int 型,当指针指向的地址中的数据的数据类型不相同,则会出错。
C++通过 new和delete关键字
-
*p的作用
分配新的内存空间,其地址已经发生改变,因此要用一个指针p指向原来的数据地址,再放到新的存储空间中
拓展:顺序存储的地址
顺序表的插入
思路:在L的位序 i 处插入元素e
(第i个位置,以及第i个位置以后的元素都要往后挪一位)
注意:位序从1开始,数组下标从0开始,所以插入的位置在 i-1 处
注意 健壮性,i的取值范围,存储空间的大小(存储空间大小是不能变化的)
#include<iostream>
#define MaxSize 10 //定义最大长度
using namespace std;
typedef struct SqList
{
int data[MaxSize]; //用静态的数组存放数据元素
int length; //表当前长度
};
void InitList(SqList &L)
{
for(int i=0;i<MaxSize;i++)
{
L.data[i] = i+1;
cout<<L.data[i]<<endl;
}
L.length=MaxSize;
}
bool ListInsert(SqList &L,int i,int e){
if(i<1 || i>L.length+1) //i的范围是[1,length]
return false; //因为后续要length+1
if(L.length >= MaxSize) //当前存储空间已满,不能插入
return false;
for(int j=L.length;j>=i;j--){ //从最后一个元素开始,将前一个元素往后移
L.data[j] = L.data[j-1];
cout<<"L.data["<<j<<"]"<<" "<<L.data[j]<<endl;
}
L.data[i-1] = e; //位序i处放入e
L.length++; //长度+1
return true;
}
int main()
{
SqList L; //建表
InitList(L); //初始化表
for(int i=0;i<MaxSize-1;i++)
{
L.data[i] = i+1;
cout<<"L.data["<<i<<"]"<<" "<<L.data[i]<<endl;
}
cout<<endl;
L.length=MaxSize-1;
cout<<endl;
ListInsert(L,2,20);
for(int i=0;i<L.length;i++){
cout<<"L.data["<<i<<"]"<<" "<<L.data[i]<<endl;
}
}
复杂度分析
最好情况: 新元素插入到表尾,不需要移动元素,则i = n + 1,循环0次, 时间复杂度为O(1)
最坏情况:新元素插入到表头,需要后移原有的n个元素,则i = 1,循环n 次,时间复杂度为O(n)
平均情况:假设新元素插入到任一位置的概率相同,即 i = 1,2,3…length+1
的概率 p = 1/n+1,i=1时循环n次,i=2时循环n-1次…i=n+1 时循环0次,则平均循环次数为 np+(n-1)p+(n-2)p+…+1p = n/2 ( 等差数列)时间复杂度为O(n)
顺序表的删除
#include<iostream>
#define MaxSize 10 //定义最大长度
using namespace std;
typedef struct SqList
{
int data[MaxSize]; //用静态的数组存放数据元素
int length; //表当前长度
};
void InitList(SqList &L)
{
for(int i=0;i<MaxSize;i++)
{
L.data[i] = i+1;
cout<<L.data[i]<<endl;
}
L.length=MaxSize;
}
bool ListInsert(SqList &L,int i,int e){
if(i<1 || i>L.length+1) //i的范围是[1,length]
return false; //因为后续要length+1
if(L.length >= MaxSize) //当前存储空间已满,不能插入
return false;
for(int j=L.length;j>=i;j--){ //从最后一个元素开始,将前一个元素往后移
L.data[j] = L.data[j-1];
cout<<"L.data["<<j<<"]"<<" "<<L.data[j]<<endl;
}
L.data[i-1] = e; //位序i处放入e
L.length++; //长度+1
return true;
}
bool ListDelete(SqList &L,int i,int &e){
if(i<1 || i>L.length) //i的范围是[1,length-1]
return false; 因为后续要length-1
而边界是 length,只能赋值到length-1
e = L.data[i-1]; //将被删除的元素赋值给e
for(int j=i;j<L.length;j++) //将第i个位置后的元素 前移
L.data[j-1] = L.data[j];
L.length--; //线性表长度减 1
return true;
}
int main()
{
SqList L; //建表
InitList(L); //初始化表
for(int i=0;i<MaxSize-1;i++)
{
L.data[i] = i+1;
cout<<"L.data["<<i<<"]"<<" "<<L.data[i]<<endl;
}
cout<<endl;
L.length=MaxSize-1;
cout<<endl;
ListInsert(L,2,20);
for(int i=0;i<L.length;i++){
cout<<"L.data["<<i<<"]"<<" "<<L.data[i]<<endl;
}
cout<<endl<<endl;
int e=-1;
if(ListDelete(L,2,e)){
cout<<"已删除第2个元素,元素值为:"<<e<<endl;
}else
cout<<"位序i不合法,删除失败";
for(int i=0;i<L.length;i++){
cout<<"L.data["<<i<<"]"<<" "<<L.data[i]<<endl;
}
}
复杂度分析
最好情况:删除表尾元素,不移动其他元素
i=n,循环0次,时间复杂度为O(1)
最坏情况:删除表头元素,后面n-1个元素全部向前移动一位
i = 1,循环n-1次,时间复杂度为O(n)
平均情况:假设删除任一元素的概率是相同的,即i=1,2,…,length概率都是
p=1/n,若i=1,循环n-1次,i=2,循环n-2次,…,i=n时,循环0 次平均循环次数 = (n-1)p+(n-2)p+…+1p = (n(n-1)/2)*1/n = (n- 1)/2时间复杂度为O(n)
顺序表的查找
按位查找
按位查找操作。即 获取表L中 第 i 个位置的元素值
注意:这个 第 i 个位置指的是位序,位序从1开始,数组从0开始,所以要取i-1
int GetElem(SeqList L, int i){
return L.data[i-1];
}
静态分配和动态分配都是这个。
问题:动态分配的方式为什么也是这样写?
因为顺序表中各个数据元素在内存中连续存放,因此通过起始地址和数据元素大小(比如 int 占4个字节,所以每4B存放一个数据元素) 可以找到第i个元素。----“随机存取”特性
时间复杂度:O(1)
按值查找
按值查找操作。即 在 表L中查找具有给定关键字的元素
int LocateElem(SeqList L,int e){ //不需要&L,因为不需要改变值
for(int i=0;i<L.length;i++) //在顺序表L中查找第一个元素值等于e的元素
if(L.data[i] == e)
return i+1; //查到找i,位序是i+1
cout<<"没有查找到";
return 0;
}
时间复杂度:
最好情况:目标在表头,循环1次:时间复杂度O(1)
最坏情况:目标在表尾,循环n次:时间复杂度O(n)
平均情况:假设目标元素出现在任一位置的概率相同,都是1/n
目标元素在第1位,循环1次,…,在第n位,循环n次
平均循环次数=1*1/n+2 *1/n+…+n * 1/n = (n+1)/2:时间复 杂度为O(n)
注意
《数据结构》考研初试中,手写代码可以直接用“==”,无论ElemType是基本数据类型还是结构类型(结构体不能直接比较,可以通过比较结构体里变量的值来进行比较)
因为手写代码主要考察学生的是否理解算法思想,不会严格要求代码完全可运行
有的学校考《C语言程序设计》,那么语法就要严格一些