线性表ADT定义如下:
ADT 线性表 (List) Data Operation
void initList(L); //创建并初始化一个空线性表,如果成功返回true,修改表传指针
bool listEmpty(L); //判断一个线性表是否为空,不修改表传值
void clearList(L); //清空一个线性表,成功返回true
bool getElem(L,i,e); //从某个位置取出元素并赋值给e(i的范围是[1,L.length]),修改e的值所以传递一个指针,成功返回true
int locateElem(L,e); //查找线性表中是否有e,如果有返回它的位置(从1开始),否则返回0表示失败
bool listInsert(L,i,e); //插入一个元素e在第i个元素之前(i的取值范围是[1,L.length+1]) ,成功返回true
bool listDelete(L,i,e); //删除在第i个位置上的元素(i的取值范围是[1,L.length]),删除的元素赋给e,成功返回true
int listLength(L); //返回线性表的元素个数
endADT
线性表有多种如下图:
顺序存储结构
线性表的顺序存储结构,指的是用一段地址
连续的存储单元
依次存储线性表的数据元素。
根据线性表的特性,我们会想到用数组来实现它,直接上代码吧,这个多说无意,最好自己去实现一遍。
注意:你会发现函数中的参数虽然都是链表,但有的传得是指针,有的传的是链表本身,会发生这种情况取决于,c语言是按值传递。
- 当参数是L时,传到函数里面的并不是链表本身,而是他的一个复制品,所以这样会导致你无法修改链表
- 当参数是*L时,则传到函数里面的是链表的地址,此时就可以通过地址来修改链表。
#include<stdio.h>
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define MAXSIZE 20 /* 存储空间初始配量 */
typedef int ElemType ; //可以自定义,这里假设为int
typedef int Status ; //Status是函数的类型,其值是函数结果状态码
typedef struct
{
ElemType data[MAXSIZE] ;
int length ;
}SqList;
/* Operation */
/* 当需要修改表的时候传入的参数是*L,查看的时候函数参数为L,因为c是按值传递,不传指针则无法修改原本的链表 */
void InitList(SqList *L) ;
bool ListEmpty(SqList L) ;
void ClearList(SqList *L) ;
bool GetElem(SqList L,int i,ElemType *e) ;
int LocateElem(SqList L,ElemType e) ;
bool ListInsert(SqList *L,int i,ElemType e) ;
bool ListDelete(SqList *L,int i,ElemType *e) ;
void ListPrint(SqList L) ;
main()
{
SqList L ;
InitList(&L) ;
if(ListEmpty(L))
printf("empty") ;
ListInsert(&L,1,1) ;
int i ;
GetElem(L,1,&i) ;
printf("%d",i) ;
printf("%d",LocateElem(L,1)) ;
ListPrint(L) ;
ListDelete(&L,1,&i) ;
ListPrint(L) ;
}
/* 初始化顺序线性表 */
void InitList(SqList *L)
{
L->length = 0 ;
}
bool ListEmpty(SqList L)
{
return L.length==0 ;
}
void ClearList(SqList *L)
{
L->length = 0 ;
}
/*在L中第i个位置之前插入新元素e,L的长度加1 */
bool ListInsert(SqList *L,int i,ElemType e)
{
int k ;
if(L->length==MAXSIZE||i<1||i>L->length+1)
return ERROR ;
if(i<=L->length) //插入元素的位置不在表尾
{
for(k=L->length-1;k>=i-1;++i)
L->data[k+1] = L->data[k] ;
}
L->data[i-1] = e ;
L->length++ ;
return OK ;
}
bool GetElem(SqList L,int i,ElemType *e)
{
if(i<1||i>L.length)
return ERROR ;
*e = L.data[i-1] ;
return OK ;
}
int LocateElem(SqList L,ElemType e)
{
int i ;
for(i=0;i<L.length||L.data[i]==e;++i)
;
return i==L.length?ERROR:i ;
}
bool ListDelete(SqList *L,int i,ElemType *e)
{
int k;
if (L->length==0) /* 线性表为空 */
return ERROR;
if (i < 1 || i>L->length) /* 删除位置不正确 */
return ERROR;
*e=L->data[i-1];
if (i < L->length) /* 如果删除不是最后位置 */
{
for(k=i;k < L->length; k++)/* 将删除位置后继元素前移 */
L->data[k-1]=L->data[k];
}
L->length--;
return OK;
}
void ListPrint(SqList L)
{
if(L.length==0)
printf("list is empty!") ;
else
{
int i ;
for(i=0;i<L.length;++i)
printf("%d->",L.data[i]) ;
}
}
至此我们的顺序存储结构完成了,那么顺序存储有什么优缺点呢?
优点: 具有简单、运算方便等优点,特别是对于小线性表或长度固定的线性表,采用顺序存储结构的优越性更为突出;
缺点:
- 顺序存储插入与删除一个元素,必须移动大了的数据元素,以此对大的线性表,特别是在元素的插入和删除很频繁的情况下,采取顺序存储很是不方便,效率低;
- 顺序存储空间容易满,出现上溢,程序访问容易出问题,顺序存储结构下,存储空间不便扩充;
- 顺序存储空间的分配问题,分多了浪费,分少了空间不足上溢。
由于顺序存储有这些缺点,于是我们就有了另外一种实现方式即链式存储结构
。
链式存储结构
链式存储不再像顺序存储一样是连续的了,它们存储在内存中并不是连续的,通过指针来将他们相连。
区别: 链式存储结构很好的解决了顺序存储结构插入和删除效率低的问题,但是他检索元素效率低。
链表的基本概念
- 数据域:我们把存储数据元素信息的域称为数据域。
- 指针域:存储直接后继位置的域称为指针域。
- 指针/链:指针域中存储的信息称做指针或链。
- 结点(Node):数据域与指针域这两部分信息组成数据元素ai的存储映像,称为结点(Node)
头指针、头结点与首元结点
对头指针概念的理解,这个很重要。“链表中第一个结点的存储位置叫做头指针”,如果链表有头结点,那么头指针就是指向头结点数据域的指针。画一个图吧。
头指针
: L存的地址是头结点的地址(如果头结点存在的话),*L表示头结点值(头结点的数据为空)头结点
: 数据域为空,下一个元素即链表中第一个元素。首元结点
: 即图中的元素a1
下图是没有头结点的情况,(有没有完全看个人兴趣,不过建议最好有,因为我发现高手写的代码里面都存在头结点,这样可以保持程序的一致性。)
好的,理解了上面这点之后,here we go !
线性表链式实现:
linkedlist.h
#ifndef _List_H
struct Node ;
typedef void* ElementType ;
typedef struct Node *PtrToNode ;
typedef PtrToNode List ;
typedef PtrToNode Position ;
List InitList() ;
List MakeEmpty(List L) ;
int IsEmpty(List L) ;
int IsLast(Position P,List L) ;
Position Find(ElementType X,List L) ;
Position FindPrevious(ElementType X,List L) ;
void Delete(ElementType X,List L) ;
void Insert(ElementType X,List L,Position P) ;
void DeleteList(List L) ;
Position Header(List L) ;
Position First(List L) ;
void PrintList(List L) ;
#endif
struct Node
{
ElementType Element ;
Position Next ;
};
linkedlist.c文件
#include<stdio.h>
#include<stdlib.h>
#include "List.h"
main()
{
int i = 1 ;
int j = 2 ;
int k = 3 ;
List L = InitList() ;
Insert(&i,L,L) ;
Insert(&j,L,L->Next) ;
Insert(&k,L,L->Next->Next) ;
//printf("%d\n",*(int*)First(L)->Element) ;
PrintList(L) ;
Delete(&j,L) ;
PrintList(L) ;
DeleteList(L) ;
PrintList(L) ;
}
List InitList()
{
List L = (List)malloc(sizeof(Node)) ;
if(!L){
printf("out of space!") ;
exit(-1) ;
}
L->Next = NULL ;
return L ;
}
int IsEmpty(List L)
{
return L->Next == NULL ;
}
int IsLast(Position P,List L)
{
return P->Next == NULL ;
}
Position Find(ElementType X,List L)
{
Position P = L->Next ;
while(P!=NULL&&X!=P->Element)
P = P->Next ;
return P ;
}
//if X not exit ,return NULL
Position FindPrevious(ElementType X,List L)
{
Position P = L ;
while(P->Next->Element!=X&&P!=NULL)
P = P->Next ;
return P ;
}
void Delete(ElementType X,List L)
{
Position P ,TempPo ;
P = FindPrevious(X,L) ;
if(P) //if X not exit P = NULL
{
TempPo = P->Next ;
P->Next = TempPo->Next ;
free(TempPo) ;
}
}
//insert X to P's back
void Insert(ElementType X,List L,Position P)
{
Position TempPo ;
TempPo = (Node*)malloc(sizeof(struct Node)) ;
if(TempPo == NULL){
printf("out of space!!!") ;
exit(-1);
}
TempPo->Element = X ;
TempPo->Next = P->Next ;
P->Next = TempPo ;
}
void DeleteList(List L)
{
Position P,Tmp ;
P = L->Next ;
L->Next = NULL ;
while(P!=NULL)
{
Tmp = P->Next ; //notice
free(P) ;
P = Tmp ;
}
}
Position Header(List L)
{
return L ;
}
Position First(List L)
{
return L->Next ;
}
void PrintList(List L)
{
if(IsEmpty(L)){
printf("list is empty!") ;
exit(-1) ;
}
Position pCurrent ;
pCurrent = L->Next ;
while(pCurrent){
printf("%d->",*(int*)pCurrent->Element) ;
pCurrent = pCurrent->Next ;
}
printf("\n") ;
}
循环链表