数据结构的整体框架如下:
线性结构的特点:在元素的非空集合中
(1)存在唯一的一个被称作“第一个”的数据元素
(2)存在唯一的一个被称作“最后一个”的数据元素
(3)除第一个之外,每个元素均只有一个前驱
(4)除最后一个之外,每个元素均只有一个后继
线性表
一:线性表的顺序表示和实现:
1.特点:逻辑相邻,物理相邻,地址连续
2.分配方式:
(1)静态分配:数组
(2)动态分配:
物理存储结构为:
Typedef struct { //若后面不再用,可省略结构名
ElemType *elem; //表基址
int length; //表长(特指元素个数)
int listsize; //表当前存储容量(字节数)
}SqList;
3.基本操作:
(1)创建(初始化)
Status InitList_Sq( SqList &L )
{ //构造一个空的线性表L。
L.elem = (ElemType*)malloc( LIST_INIT_SIZE * sizeof( ElemType) );//申请内存区域,返回首地址
If ( ! L.elem )
exit ( OVERFLOW ) ; // 存储分配失败
L.length = 0; // 空表当前长度为0
L.listsize = LIST_INIT_SIZE; // 初始存储容量
return OK;
} // InitList_Sq
(2)向第i个位置插入一个元素
status listinsert ( &L,i,e) {
if (i<1 || i>L.len+1) return ERROR; // i值不合法
if (L.len = = L.lsize) { // 当前存储空间已满,增加分配
newbase = (Elemtype * ) realloc(L.elem, (L.listsize +
LISTINCREMENT) * (sizeof(ElemType));
if (!newbase) exit(overflow); // 存储分配失败
L.elem = newbase; // 新基址
L.listsize += LISTINCREMENT; } // 增加存储容量
q = &(L.elem[i-1]); // q为插入位置
for (p = &(L.elem[L.len-1]);p>=q;--p)
*(p+1) = * p; // 元素后移
*q = e; ++L.len; // 插入e,表长增1
return OK;
} //listinsert
(3)向线性表中第i个位置删除一个元素
Status ListDelete ( &L,i,&e)
{
if (i<1 || i>L.len )
return ERROR; // 删除位置不合法
p = &(L.elem[i-1]); // p 为被删除元素的位置
e = *p; // 被删除元素的值赋给 e
q = L.elem + L.len – 1; // 表尾元素的位置地址
for (p; p<q; ++p)
*p =*(p+1); // 被删除元素之后的元素左移
-- L.len; // 表长减1
return OK;
} // ListDelete
4.时间复杂度+空间复杂度分析
插入平均移动次数为:n(n+1)/2÷(n+1)=n/2 ≈ O(n)
删除平均移动次数为: n(n-1)/2÷n=(n-1)/2≈ O(n)
5.优缺点分析:
优点:可以随机存取表中任一元素(查找方便)
缺点:在插入,删除某一元素时,需要移动大量元素(非查找不方便)
二:线性表的链式表示和实现:
1.存储结构:Typedef struct Lnode{
ElemType data;
Struct Lnode*next;
}Lnode,*LinkList
2.基本操作:
(1)单链表的建立
*先进后出
Void CreateLinkList(LinkList &L)
{
//首先建立一个头结点
L=(LinkList)molloc(sizeof(Lnode));
L->next=null;
//先进后出建立n个元素的链表
For(i=n;i>0;i--)
{
p=(LinkList)malloc (sizeof(LNode)); // 生成新结点
cin>>p->data; // 输入元素值
p→next = L→next ;
L→next = p; // 插入到表头
}
}
*先进先出void CreateLinkList(LinkList &L)
{
//首先建立一个头结点
L=(LinkList)molloc(sizeof(Lnode));
L->next=null;
head=L;
//先进后出建立n个元素的链表
For(i=n;i>0;i--)
{
p=(LinkList)malloc (sizeof(LNode)); // 生成新结点
cin>>p->data;// 输入元素值
head->next=p;//使头指针指向p
p->next=null;//p的指向为空
head=p->next;//head移到下一个
}
}
(2)单链表的查找 status GetElem_L(LinkList L, int i, ElemType &e)
{
// L为带头结点的单链表的头指针
// 当第i个元素存在时,其值赋给e并返回OK,否则返回ERROR
P = L->next; j=1; // 初始化 ,p指向第一个结点,j为计数器
while(p&&j<i)
{ p=p->next; ++j;}
// 顺指针向后查找,直到p指向第i个元素或p为空
if(!p||j>i) return ERROR; // 第i个元素不存在
e=p->data; // 取第i个元素
return OK;
} // GetElem_L
(3)单链表的插入
status ListInsert(LinkList &L,int i,ElemType e )
{ //在带头结点的单链表L中第i个位置之前插入元素e
p = L; j = 0;
while (p&& j<i-1 ) { p = p -> next; ++j;}
if (! p || j> i-1) return ERROR;
s = (LinkList) malloc (sizeof(LNode));
s -> data=e; s->next= p -> next;
p -> next = s;
return OK;
} // ListInsert_l
(4)单链表的删除\
Status ListDelete_L(LinkList &L,int i,ElemType &e) {
// 删除以L为头指针(带头结点)的单链表中第i个结点
p = L; j = 0;
while (p->next && j < i-1)
{ p = p->next; ++j; }
// 寻找第 i 个结点,并令 p 指向其前趋
if (!(p->next) || j > i-1)
return ERROR; // 删除位置不合理
q = p->next; p->next = q->next; // 删除并释放结点
e = q->data; free(q);
return OK;
} // ListDelete_L
(5)两有序单链表的有序合并
Void MergeList_L(LinkList &La,LinkList &Lb,LinkList &Lc)
{ //按值排序的单链表LA,LB,归并为LC后也按值排序
pa=La-->next; pb=Lb-->next; Lc=pc=La; //初始化
while(pa&&pb) //将pa 、pb结点按大小依次插入C中
{ if(pa->data<=pb->data)
{pc->next=pa; pc=pa; pa=pa->next;}
else {pc->next=pb; pc=pb; pb=pb->next}
}
pc->next = pa?pa:pb ; //插入剩余段
free(Lb); //释放Lb的头结点
} //MergeList_L
3其它链表的形式
(1)双向链表:有两个指针域的链表,称为双链表
特点:可以双向查找表中结点
Typedef struct Lnode{
ElemType data;
Struct Lnode*prior,*next;
}Lnode,*LinkList
插入:
删除:
(2)循环链表:将表中最后一个结点的指针域指向头结点( p->next=head),整个链表形成一个环。
特点:从任一结点出发均可找到表中其他结点。
4.时间效率+空间效率分析
时间效率分析:
查找: 因线性链表只能顺序存取,即在查找时要从头指针找起,查找的时间复杂度为 O(n)。
插入和删除: 因线性链表不需要移动元素,只要修改指针,一般情况下时间复杂度为 O(1)。
但如果要在单链表中进行前插或删除操作,由于要查找前驱结点,所耗时间复杂度为 O(n)。
空间效率分析:
链表中每个结点都要增加一个指针空间,相当于总共增加了n个整型变量,空间复杂度为 O(n)。
小结:(1)顺序表是用数组实现的,而链表是用指针来实现的。
(2)当线性表的长度变化较大,难以估计其存储规模时,益采用动态链表作为存储结构为佳;当线性表的长度变化不大,易于事先确定其大小时,为了节约存储空间,宜采用顺序表作为存储结构。(基于空间的考虑)
(3)顺序表是一种随机存取结构,对表中任一结点都可在O(1)时间内直接存取。而链表中的结点则需用从头指针开始顺链扫描。
因此:若线性表的主要操作是查找,很少涉及插入、删除时,可采用顺序表结构;若要频繁进行插入和删除,宜采用链表结构;若表的插入、删除操作主要发生在表的两端,则宜采用有尾指针的单循环链表。