赞
踩
线性表中数据元素之间的关系是一对一的关系,即除了第一个和最后一个数据元素之外,其它数据元素都是首尾相接的。
线性表有两种物理存储结构,顺序存储结构和链式存储结构。
顺序存储结构封装一般需要3个属性:
1.数据起始地址(数组名)
2.最大储存容量(一般初始化后就不变,除非要动态扩容)
3.线性表的当前长度(元素个数是会变化的)
线性表的基本操作:
InitList(*L): 初始化一个线性表
ListEmpty(L): 判断是否为空表,空为True,非空为False
ClearList(*L): 清空线性表
GetElem(L,i,*e): 将表中的第i个元素,返回给e
LocateElem(L,e): 查找表中是否有与e相等的元素。成功,返回序号;若失败,返回0。
ListInsert(L,i,e): 在线性表的第i个位置,插入新元素e
ListDelete(*L,i,*e): 删除线性表中的第i个元素,并将值返回给e
ListLength(L): 返回线性表元素的个数
顺序存储结构:
优点:
1.无需为表示表中元素之间的逻辑关系而增加额外的存储空间
2.可以快速的存取表中的任意位置的元素
缺点:
1.插入和删除需要移动大量元素(中间没有间隙)
2.当线性表长度变化较大的时候,难以确定储存空间的容量
3.容易造成存储空间的“碎片”(两个大型的线性表之间间隔的地址)
链式存储结构属性:数据域和指针域,两者的结合称为Node。两个元素之间的地址未必相连,只能通过前一个元素的指针域指向后一个元素的指针域判断。
头指针为单链表必要元素,指向头结点,头结点的数据域通常描述整个链表的当前元素长度,再指向第一个有效元素结点。
链表尾的结点指针指向NULL(有头结点的空链表,头指针指向头结点;无头结点的空链表,头指针直接指向NULL)
链表结点属性:
typedef struct node
{
Elemtype data; //数据域
struct node *next; //指针域
}node;
typedef struct node* Linklist; (Linklist L == struct node *L);
GetElem():获取单链表第i个元素的数据域
创建一个指针p,p指向L的第一个结点,初始化计数j=1;
当j < i时遍历链表,直到j = i,返回当前数据域;
如果p最后指向NULL或者j>i,则该结点无效。
typedef int Status typedef int Elemtype; Status GetElem(Linklist L,int i,Elemtype *e) { int j; Linklist p;//定义临时指针p p = L->next; j = 1; while(p && j < i) //如果p非空 且 j < i { p = p->next; j++; } if(!p || j > i) { return ERROR; //#define ERROR 1; } *e = p->data; return OK; //#define OK 0; } 时间复杂度:O(n)
ListInsert():将元素插入单链表某个位置
Status ListInsert(Linklist *L,int i,Elemtype e) { int j; Linklist temp,new; j = 1; temp = *L; while(temp && j < i) //找到j=i,temp是目标结点的前一个结点 { temp = temp->next; j++; } if(!temp || j > i) { return ERROR; } new = (Linklist)malloc(sizeof(node));//动态申请内存,链表的意义(随时扩容) new->data = e; new->next = temp->next; temp->next = new; return OK; } 时间复杂度O(n)
ListDelete():单链表删除某个结点
Status ListDelete(Listlink *L,int i,Elemtype *e) { int j; Linklist temp,temp2; j = 1; temp = *L; while(temp && j < i) //找到要删除的前一结点 { temp = temp ->next; j++; } if(!temp || j > i) { return ERROR; } temp2 = temp->next; //要删除的结点 *e = temp2->data; temp->next = temp2 ->next; free(temp2); return OK; } 时间复杂度O(n)
创建单链表的过程是一个动态生成链表的过程,从“空表”的初始状态,依次建立各元素结点并逐个插入链表。
算法思路:
1.声明结点p和计数器i;
2.初始化空链表L;
3.将L的头结点指针域指向NULL,形成一个带头结点的空链表;
4.循环实现后继结点的赋值和插入;
头插法,将新元素放在表头的第一个位置
CreatListHead():
void CreatListHead(Linklist *L,int n) { Linklist p; int i; srand(time(0)); *L = (Linklist)malloc(sizeof(struct node)); //申请头结点 (*L)->next = NULL; //头结点指向NULL for(i = 0;i < n;i++) { p = (Linklist)malloc(sizeof(struct node)); p->data = rand()%100+1; p->next = (*L)->next; (*L)->next = p; } }
尾插法,将元素放在最后一个位置
Void CreatListTail(Linklist *L,int n) { Linklist p,temp; int i; *L =(Linklist)malloc(sizeof(struct node)); (*L)->next = NULL; temp = *L;//指向当前链表的末尾结点 srand(time(null)); for(i = 0; i < n ;i++) { p = (Linklist)malloc(sizeof(struct node));//新结点 p->data = rand%100 + 1; temp->next = p; temp = p; } temp->next = NULL;//最后结点指向NULL }
单链表的整表删除(内存释放)
Status ClearList(Linklist *L)
{
Linklist p,q;
p = (*L)->next;//指向头结点
while(p)
{
q = p->next;
free(p);
p = q;
}
return OK;
}
链式存储结构与顺序存储结构优缺点对比:
1.时间性能
(1)查找
顺序存储结构O(1)
单链表O(n)
(2)插入和删除
顺序存储结构O(n),要进行数据位置的平移
单链表O(1),找到位置,直接操作
2.空间性能
(1)顺序存储结构要预先规划
(2)单链表无限制,可动态扩展
所以,项目中如果经常需要查找操作,可使用顺序存储结构;如果要进行动态的操作,则适合链式存储结构。
静态链表
1.静态链表规定数组的第一个元素和最后一个元素的data不能有数据。
2.元素的游标指向数组下标。
3.数组尾的游标指向第一个有数据元素的下标。
4.数组头的游标指向静态链表的结尾(有数据的后一个,即备用链表的第一个结点的下标)。
5.最后一个有数据的元素游标指回下标0(链表头)。
6.其余有效元素游标都是指向游标的下一个有效元素的下标(第一个元素的游标是50,下一个元素的下标即是50)。
通常把未使用数组元素称为备用链表。
静态链表存储结构:
#define Maxsize 1000
typedef int Elemtype;
typedef struct
{
Elemtype data; //数据
int cur; //游标
}component,staticLinklist[Maxsize];//下标
静态链表初始化:
Status staticLinklistInit(staticLinklist space)
{
int i;
for(i = 0;i < Maxsize-1;i++)//下标从0开始
{
space[i].cur = i+1;//游标从1开始
}
space[Maxsize-1].cur = 0;//目前是空的
return OK;
}
静态链表插入,首先先要找到备用链表的首下标
int malloc_SLL(staticLinklist space)//获取备用链表的下标 { int i; i = space[0].cur;//将第一个备用链表的下标赋给i if(space[0].cur) { space[0].cur = space[i].cur;//将静态链表头指向下一个备用链表 } return i; } Status staticLinklistInsert(staticLinklist L,int i,Elemtype e) { int j,k,l; //第i个元素之前插入新元素 k = Maxsize -1; if(i < 1 || i > Maxsize) //插入位置不合法 { return ERROR; } j = malloc_SLL(L); //获取要填充的下标 if(j) { L[j].data = e; // e是新插入的元素 for( l=1; l <= i-1; l++ ) // 要插到第一个结点后,i=2,则循环执行1次 { k = L[k].cur; // 找到插入位置的前一个结点下标 } L[j].cur = L[k].cur; // 插入的新结点的游标指向原来的第i个 L[k].cur = j; //将i-1的结点游标指向要填充的下标 return OK; } return ERROR;
静态链表的删除(数据依然存在,但已不在有效元素链上)
Status staticLinklistDelete(staticLinklist L,int i,Elemtype *e) { int j,k; if(i < 1 || i > Maxsize) { return Error; } k = Maxsize - 1; for(j = 1; j < i - 1;j++)//先找要删除的前一个有效结点的下标, //例删除上表的第2个结点,i=2 { k = L[k].cur; //k=1 } j = L[K].cur; //找到要删除结点的下标,j=2 //L[j].Data = 0; L[k].cur = L[j].cur;//完成删除 L[j].cur = L[0].cur;//连接下一备用结点 L[0].cur = j; //将删除的结点作为第一个备用链表; }
计算静态链表的有效元素个数
int staticListLen(staticLinklist L)
{
int i = 0;
int j = L[Maxsize-1].cur;
while(j)
{
j = L[j].cur;
i++;
}
return i;
}
静态链表优点:
1.在插入和删除操作时,不需要移动元素,只需要改变游标,从而改进了在顺序存储结构中要移动大量元素的缺点。
静态链表缺点:
1.没有解决动态扩展,数组长度不能动态扩展
2.不能直接通过数组下标进行随机存储。
快慢指针找未知长度单链表的中间结点:
设置两个指针同时指向链表的头结点,其中快指针的移动速度是慢指针的两倍,当快指针指到末尾结点时,慢指针正好在中间结点。
Status Getmidnode(ListLink L,Elemtype *e) { ListLink search,mid; search = mid = L; while(search->next != NULL) { mid = mid->next; if(search->next->next != NULL) { search = search->next->next; } ELSE { search = search->next;//快指针的后后结点为空,后结点不为空时,找尾 } } *e = mid->data; return OK; }
循环链表:
将链表的尾结点的指针域指回头结点,使其形成一个环,这种称为单循环链表,也成为循环链表。
typedef int Elemtype;
typedef struct node
{
Ememtype data;
struct node *next;
}Node;
typedef struct node* LinkClist;
CycleListInit();
void CycleListInit(LinkCList *CL) { LinkCList temp , rear; Elemtype i; printf("输入结点的值,输入0则完成初始化\n"); while(1) { scanf("%d",&i); //fflush(stdin); scanf("%*[^\n]%*c"); srand(time(0)); if(i == 0) { return; } if((*CL) == NULL)//空表 { *CL = (LinkCList)malloc(sizeof(Node)); if(!(*CL)) { printf("内存创建失败"); exit(1); } (*CL)->data = i; (*CL)->next = *CL; } else { for(rear = (*CL); rear->next != (*CL);rear = rear->next);//找到末尾结点 temp = (LinkCList)malloc(sizeof(Node)); if(!temp) { printf("内存创建失败"); exit(1); } temp->data = i; temp->next = *CL;//新结点指向头 rear->next = temp;//原来末尾的指向新结点 } } }
CycleListInsert();
void CycleListInsert(LinkCList *CL,int i) { LinkCList temp,rear,newnode; int j,newdata; printf("输入插入结点的值:"); scanf("%d",&newdata); if(i == 1) { newnode = (LinkCList)malloc(sizeof(Node)); if(!newnode) { printf("插入结点内存分配失败\n"); exit(1); } newnode->data = newdata; for(rear = *CL;rear->next != *CL;rear = rear->next); newnode->next = *CL; rear->next = newnode; *CL = newnode; } else { temp = *CL; for(j = 1; j < i-1;j++) { temp = temp->next; } newnode = (LinkCList)malloc(sizeof(Node)); if(!newnode) { printf("插入结点内存分配失败\n"); exit(1); } newnode->data = newdata; newnode->next = temp->next; temp->next = newnode; } }
CycleListDelete();
void CycleListDelete(LinkCList *CL,int i) { LinkCList temp,target; int j = 1; if(i == 1) { for(target = *CL;target->next != *CL;target = target->next); temp = *CL; *CL = (*CL)->next; target->next = *CL; free(temp); } else { target = *CL; for(j = 1; j < i-1;j++) { target = target->next; } temp = target->next; target->next = temp->next; free(temp); } }
双向链表
同时拥有前驱和后驱,第一个有效节点的前驱指向末尾,末尾结点的后驱指向第一个有效指针。
结点结构:
typedef struct Dualnode
{
Elemtype data;
struct node *prior;//前驱
struct node *next;//后驱
}DualNode;
typedef struct Dualnode * LinkDList;
插入操作:
s->next = p;
s->prior = p->prior;
p->prior->next = s;
p->prior = s;
删除操作:
p->prior->next = p->next;
p->next->prior = p->prior;
free(p);
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。