线性表(大话数据结构)

1.线性表抽象数据类型
2.线性表顺序存储
3.线性表的链式存储结构
4.其他链表

1.线性表抽象数据类型

ADT 线性表(list)
Data
Operation

  • *InitList(L): 初始化操作,建立一个新的线性表L
  • ListEmpty(L): 若线性表为空,返回 true ,否则返回 false
  • *ClearList(L): 将线性表清空
  • *GetElem(L,i,e): 将线性表 L 中第 i 个位置元素值返回给 e
  • LocateElem(L,e): 在线性表 L 中查找与给定值 e 相等的元素,如果查找成功,返回该元素在表中序号表示成功,否则返回 0 表示失败
  • *ListInsert(L,i,e): 在线性表 L 中的第 i 个位置插入新元素 e
  • ListDelete( L,i,e): 删除线性表 L 中第 i 个位置元素,用 e 返回其值
  • ListLength(L): 返回线性表 L 的元素个数
/*将所有在线性表lb中但不在la中的数据元素插入到la中*/
void unionL(Sqlist *la,Sqlist lb)
{
	int la_len,lb_len,i;
	int e;						//e 为la.lb相同元素
	la_len = ListLength(*la);	
	lb_len = ListLength(lb);
	for (i = 1;i < lb_len;i++)
	{
		GetElem(lb,i,&e);		//取lb中第i个元素赋给e
		if (!LocateElem(*la,e))	//如果la中不存在与e相同元素
		{
			ListInsert(la,++la_len,e);	//将e插入la并增长la_len
		}
	}
}

```

2.线性表顺序存储

结构代码

#define MAXSIZE 20					//宏定义改变初始分配量
typedef int ElemType;				//全局改变线性表存储类型
typedef struct
{
    ElemType data[MAXSIZE];			//数组,存储数据元素
    int lengh;						//线性表的当前长度
}Sqlist;

i 个数据元素 ai 存储地址可由 ***a1***表示

LOC(ai) = LOC(a1) + (i-1) c*

获取数据元素 o(1)

获取第 i 个位置元素的值

/*初始条件:顺序线性表 L 已存在,1 <= i <=ListLength(L) */
/*操作结果:用 e 返回 L 中第 i 个元素的值, i 为位置,第 1 个位置是a[0]*/
int GetElem(Sqlist L,int i,int *e)
{
    if(L.length == 0 || i < 1 || i > L.lengh)	//if 获取的位置过小或过大或L的长度为0,返回0
    {
        return 0;
    }
    *e = L.data[i-1];			//返回第 i 个位置的值给 e 
    
    return 1;
}

插入操作 o(n)

在线性表第 i 个位置插入 e

  1. 插入位置不合理时抛出异常
  2. 线性表长度大于等于数组长度时抛出异常或动态增加容量
  3. 从最后一个元素开始向前遍历到第 i 个位置,分别将它们都向后移动一个位置
  4. 将要插入的元素填到位置 i
  5. 表长加1
/*初始条件:顺序线性表 L 已存在,1 <= i <= ListLength(L)	*/
/*操作结果:在 L 中第 i 个之前插入新的数据元素 e ,L 的长度加1	*/
int ListInsert(Sqlist *L,int i,int e)
{
    int k;
    if (L->length == MAXSIZE)			//顺序线性表已满
    {
        return 0;
    }
    if (i < 1 || i > L->length + 1)		//插入位置小于第一个或大于最后一个的后一个
    {
        return 0;
    }
    if (i <= L->length)					//若插入的位置不在表尾
    {
        for (k = L->length - 1;k >= i - 1;k--)		//将要插入位置后的元素向后移一位
        {
            L->data[k+1] = L->data[k];
        }
    }
    L->data[i-1] = e;
    L->length++;
    
    return 1;
}
    

删除操作 o(n)

删除第 i 个数据元素并用 e 返回其值

  1. 如果删除位置不合理,抛出异常
  2. 取出删除元素
  3. 从删除元素位置开始遍历到最后一个元素位置,分别将它们都向前移动一个位置
  4. 表长减1
/*初始条件:L 已存在,1 <= i <= ListLength(L)	*/
/*操作结果:删除 L 的第 i 个数据元素,并用 e 返回其值,表长-1	*/
int Listdelete(Sqlist *L,int i,int *e)
{
    int k;
    if (L->length == 0 || i < 1 || i > L->length)
    {
        return 0;
    }
    *e = L->data[i-1];
    if (L->length)						//如果删除的不是最后位置
    {
        for (k = i;k < L->length;k++) 	//将删除位置后继元素前移
        {
            L->data[k-1] = L->data[k];
        }
    }
    L->length--;
    
    return 1;
}

读取 o(1)

插入 o(n)

删除 o(n)

  • 优点:
    • 无须增加额外存储空间
    • 快速存取表中任意位置元素
  • 缺点:
    • 插入和删除操作复杂度高
    • 当线性表长度变化较大时,难以确定存储空间的容量
    • 造成存储空间“碎片”

3.线性表的链式存储结构

因为线性表的顺序存储在插入与删除时需移动大量元素

所以引入线性表链式存储


  • 头指针
    • 是链表指向第一个结点的指针,若链表有头结点,则是指向头结点的指针
    • 常用头指针冠以链表名字
    • 无论链表是否为空,头指针均不为空,头指针是链表必需元素
  • 头结点
    • 为操作统一和方便设立的,放在第一元素之前,数据域一般无意义(可存储链表长度)
    • 有头结点后,对第一元素结点前插入与删除操作就统一了
    • 头结点不一定是链表必须元素
/*线性表单链表存储结构	*/
typedef struct Node
{
    int data;
    struct Node *next;
}Node;
typedef struct Node *LinkList;		//定义链表

单链表的读取 o(n)

获取链表第 i 个元素数据:

  1. 声明 p 指向第一个结点,j 初始化为1
  2. j<i 时遍历链表,让 p 向后移动,j++
  3. 若到链表尾部 p 为空,则第 i 个结点不存在
  4. 否则为查找成功,返回 p->data
/*初始条件:线性表 L 已存在,1<=i<=ListLength(L)	*/
/*操作结果:用 e 返回 L 中第 i 个元素的值			*/
int GetElem(LinkList L,int i,int *e)
{
    int j;
    LinkList p;
    p = L->next;			//让 p 指向 L 的第一个结点
    j = 1;
    while (p && j < i)		//当 p 不为空且未遍历到第 i 个元素时
    {
        p = p->next;
        j++;
    }
    if (!p || j > i)
    {
        return 0;			//第 i 个元素不存在
    }
    *e = p->data;
    return 1;
}

单链表的插入(头插) o(n)

将结点 s 插到 pp->next 之间

s->next = p->next;

p->next = s;

给单链表第 i 个数据插入结点

  1. 声明指针 p 指向表头结点,j = 1
  2. j < i 时,遍历链表,p 向后指,j++
  3. 若到末尾 ***p***为空,则第 i 个结点不存在
  4. 否则查找成功,生成 s 结点
  5. e 赋值给 s->data
  6. 执行头插上述代码
/*初始条件:线性表 L 已存在,1<=i<=ListLength(L)		*/
/*操作结果:在 L 中第 i 个位置之前插入新的元素 e ,L长度++ */
int ListInsert(LinkList *L,int i,int e)
{
    int j;
    LinkList p,s;
  	p = *L;						//操作统一
    j = 1;
    while (p && j < i)
    {
        p = p->next;
        j++;
    }
    if (!p || j > i)
    {
        return 0;
    }
    s = (LinkList)malloc(sizeof(Node));		//生成新结点
    s->data = e;
    s->next = p->next;
    p->next = s;
    return 1;
}

单链表的删除 o(n)

p q/p->next q->next/p->next-next

a(i-1) ai a(i+1)

删除 存储ai 的 结点时,

q = p->next;

p->next = q->next;

删除单链表第 i 个结点的思路

  1. p 指向头结点,*** j = 1***
  2. j < i 时,遍历链表,p 向后指,j++
  3. 若到末尾 ***p***为空,则第 i 个结点不存在
  4. 否则查找成功,将要删除的结点 p->next 赋给 q
  5. p->next = q->next
  6. 返回 q 结点数据给 e
  7. 释放 q
/*初始条件:线性表 L 已存在,1<=i<=ListLength(L)		*/
/*操作结果:删除 L 的第 i 个数据元素,并用 e 返回值,L 长度减 1 */
int ListDelete(LinkList *L,int i,int *e)
{
    int j = 1;
    LinkList p,q;
    p = *L;
    while(p->next && j < i)
    {
        p = p->next;
        j++;
    }
    if (!(p->next) || j > i)
    {
        return 0;
    }
    q = p->next;			//q 指向删除元素结点
    p->next = q->next;
    *e = q->data;
    free(q);
    return 1;
}

整表创建

/* 随机产生 n 个元素的值,建立带有头结点的单链表 L	(头插法) */
void CreatListHead (LinkList *L,int n)
{
    LinkList p;
    int i;
    srand(time(0));
    *L = (LinkList)malloc(sizeof(Node));
    (*L)->next = NULL;			//建立一个带头结点的单链表
    for (i = 0;i < n;i++)
    {
        p = (LinkList)malloc(sizeof(Node));		//生成新结点
        p->data = rand()%100 + 1;				//随机生成100以内的数字
        p->next = (*L)->next;
        (*L)->next = p;							//插入到表头
    }    
}

/*尾插法创建单链表 */
void CreatListhead (LinkList *L,int n)
{
    LinkList p,r;
    int i;
    srand(time(0));
    *L = (LinkList)malloc(sizeof(Node));
    r = *L;									//r 指向尾部的结点
    for (i = 0;i < n;i++)
    {
        p = (Node*)malloc(Node);			//生成新结点
        p->data = rand()%100 + 1;			//随机生成100以内随机数
        r->next = p;						//将表尾指针 r 指向新结点
        r = p;								//此时 p 为表尾结点,更新 r 
    }
    r->next = NULL;
}

单链表整表删除

int ClearList(LinkList *L)
{
    LinkList p,q;
    p = (*L)->next;					// p 指向第一个结点
    while(p)						//没到表尾时
    {
        q = p->next;
        free(p);
        p = q;
    }
    (*L)->next = NULL;				//第一个结点为空
}

1	2	3
()	()	()	NULL
p	q

	2	3	NULL
    ()	()
    p,q
    
	2	3	NULL
    ()	()
    p	q
    
    	3	NULL
    	()
    	p,q
    
    		NULL
    		p,q

4.其他链表

静态链表

typedef struct{
    int data;
    int cur;			//游标,为 0 时无指向
} Component,StaticLinkList[1000];

/* 初始化	*/
/* 数组第一个与最后一个的数据域不存放数据*/
/*	space[0].cur = 备用链表第一个结点的下标
	链表满时==无备用链表,此时space[0].cur = 0
	space[999].cur = 第一个有数值的元素的下标
	每个元素的 cur 存放下一个元素的下标 若下一位置数据为空,则 cur = 0	*/

int InitList(StaticLinkList space)
{
    int i;
    for (i = 0;i < 1000 - 1;i++)
    {
        space[i].cur = i + 1;				//i = 0 时,space[0].cur = 1;(备用链表第一个结点下标为 1 )
    }										//i = 1 时,space[1].cur = 2;(通过访问space[1].cur的值找到下一个位置
    										//							数据的下标
    space[999].cur = 0;						//space[999].cur 存放第一个有数值的元素的下标,初始化时为 0 
    return 1;
}
静态链表的插入
/* 若备用链表非空,则返回分配的结点下标,否则返回 0 */
int Malloc_SSL(staticLinkList space)
{
    int i = space[0].cur;					//i 为备用链表第一个结点的下标
    if (space[0].cur)						//当还有备用链表时
    {
        space[0].cur = space[i].cur;		//因为已经确认要用第 i 个下标,此时备用链表的第一个结点的下标时原来备用链表的											 //第一个结点的游标的值
    }
    return i;
}

int ListInsert(StaticLinkList L,int i,int e)
{
    int j,k,l;
    k = MAXSIZE - 1;					// K 为最后一个元素的下标
    if ( i < 1 || i > ListLength(L) + 1)
    {
        return 0;						//插入位置不合理
    }
    j = Malloc_SSL(L);					//获得备用链表的下标
    if (j)								//存在备用链表时
    {
        L[j].data = e;					//填充新结点
        for ( l = 1;l <= i - 1;l++)		//找到第 i 个元素之前的位置
        {
            k = L[k].cur;
        }
        L[j].cur = L[k].cur;			//新结点的游标(下一个结点的下标) = 第 i 个元素之前的 cur
        L[k].cur = j;					//将新元素的下标赋给第 i 个元素之前元素的 cur
        return 1;
    }
    return 0;
}
静态链表的删除
/* 删除在 L 中第 i 个元素 */
int ListDelete(StaticLinkList L,int i)
{
    int j,k;
    if ( i < 1 || i > ListLengeth(L))
    {
        return 0;						//删除位置不合理
    }
    k = MAXSIZE - 1;
    for ( j = 1;j <= i - 1;j++)			//找到第 i 个元素之前的位置
    {
        k = L[k].cur;
    }
    j = L[k].cur;						// j 为要删除的元素的下标
    L[k].cur = L[j].cur;				//跳过 j ,连接
    Free_SSL(L,j);						//释放 L 的第 j 个元素
    return 1;
}

void Free_SSL(StaticLinkList space,int k)
{
    space[k].cur = space[0].cur;		// 第 k 个结点的游标为原来备用链表第一个元素下标
    space[0].cur = k;					//此时 k 为备用链表的第一个元素的下标
}
求静态链表的长度
/* 返回静待链表 L 元素的个数	*/
int ListLength(StiticLinkList L)
{
	int j = 0;
    int i = L[MAXSIZE - 1].cur;			// i 为链表第一个有元素的元素的下标
    while(i)							//没到链表尾部时
    {
        i = L[i].cur;
        j++;
    }
    return j;
}

静态链表优缺点

  • 优点
    • 在插入和删除时只需要修改游标,不需移动元素
  • 缺点
    • 表长难以确定
    • 无随机存储特性

循环链表

存在头结点,最后一个结点的下一个结点是头结点

  • 头指针指向头结点时,访问到第一个结点 o(1) ,访问最后一个结点 o(n)
  • 头指针指向尾结点时,访问到第一个结点 o(1) ,访问最后一个结点 o(1)
合并两个循环链表
/* rearA 指向 A 表尾结点
   rearB 指向 B 表尾结点	*/

p = rearA->next;						//保存 A 表头结点
rearA->next = rearB->next-next;			// A 表尾结点指向 B 表第一个数据结点

q = rearB->next;						//保留 B 表头结点,不然等会删的时候找不到
rearB->next = p;						//此时原 B 表的尾结点为合并表的尾结点,所以赋值下一个结点为整体的头结点
free(q);								//删除原 B 表的头结点(一个链表只需一个头结点)
判断链表是否有环(快慢指针法)
int Hasloop(LinkList L)
{
    LinkList p = L;
    LinkList q = L;
    
    while(p != NULL && q != NULL && q->next != NULL)
    {
        p = p->next;
        q = q->next-next;
        
        if(p == q)
        {
            return 1;
        }
    }
    return 0;
}
双向链表
/* 线性表的双向链表存储结构 */
typedef struct DulNode
{
    int data;
    struct DulNode *prior;				//直接前驱指针
    struct DulNode *next;				//直接后继指针
} DulNode,*DuLinkList;

双向循环链表

p->next-prior = p = p->prior->next;

双向链表的插入
/* 将结点 s(s->data = e) 插入到结点 p 与 p->next 之间 */
s->prior = p;							// p 赋给 s 的前驱
s->next = p->next;						// p 的后继赋给 s 的后继
p->next->prior = s;						// s 赋给 p 的后继的前驱
p->next = s;							// s 赋给 p 的后继

/* 先处理 s 的前驱与后继,再处理后结点的前驱,最后处理前结点的后继
双向链表的删除
/* 删除结点 p */
p->prior->next = p->next;				// p 的前驱的后继是 p 的后继
p->next-prior = p->prior;				// p 的后继的前驱是 p 的前驱
free(p);

----------------------------------线性表------------------------------------

顺序存储结构 ------------------------------链式存储结构----------------------------------

单链表-----------------静态链表----------------循环链表 -----------------双向链表

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值