当前位置:   article > 正文

线性表ADT(List)

线性表adt

  线性表中数据元素之间的关系是一对一的关系,即除了第一个和最后一个数据元素之外,其它数据元素都是首尾相接的。
  线性表有两种物理存储结构,顺序存储结构和链式存储结构。

顺序存储结构封装一般需要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
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

顺序存储结构:
优点:
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);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

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)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

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)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

  创建单链表的过程是一个动态生成链表的过程,从“空表”的初始状态,依次建立各元素结点并逐个插入链表。
算法思路:
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;
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

尾插法,将元素放在最后一个位置

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
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

单链表的整表删除(内存释放)

Status ClearList(Linklist *L)
{
	Linklist p,q;
	p = (*L)->next;//指向头结点
	
	while(p)
	{
		q = p->next;
		free(p);
		p = q;
	}
	return OK;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

链式存储结构与顺序存储结构优缺点对比:
 1.时间性能
  (1)查找
    顺序存储结构O(1)
    单链表O(n)
  (2)插入和删除
    顺序存储结构O(n),要进行数据位置的平移
    单链表O(1),找到位置,直接操作
 2.空间性能
  (1)顺序存储结构要预先规划
  (2)单链表无限制,可动态扩展
  所以,项目中如果经常需要查找操作,可使用顺序存储结构;如果要进行动态的操作,则适合链式存储结构。


  静态链表
s
  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];//下标
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

静态链表初始化:

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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

静态链表插入,首先先要找到备用链表的首下标

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;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

静态链表的删除(数据依然存在,但已不在有效元素链上)

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;		//将删除的结点作为第一个备用链表;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

计算静态链表的有效元素个数

int staticListLen(staticLinklist L)
{
	int i = 0;
	int j = L[Maxsize-1].cur;
	
	while(j)
	{
		j = L[j].cur;
		i++;
	}
	return i;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

静态链表优点:
  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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

循环链表:
  将链表的尾结点的指针域指回头结点,使其形成一个环,这种称为单循环链表,也成为循环链表。

typedef int Elemtype;
typedef struct node
{	
	Ememtype data;
	struct node *next;	
}Node;
typedef struct node* LinkClist;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

  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;//原来末尾的指向新结点
        }
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46

  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;
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41

  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);
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

双向链表
  同时拥有前驱和后驱,第一个有效节点的前驱指向末尾,末尾结点的后驱指向第一个有效指针。

结点结构:

typedef struct Dualnode
{
	Elemtype data;
	struct node *prior;//前驱
	struct node *next;//后驱
}DualNode;
typedef struct Dualnode * LinkDList;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

插入操作:

s->next = p;        
s->prior = p->prior;        
p->prior->next = s;        
p->prior = s;
  • 1
  • 2
  • 3
  • 4

删除操作:

p->prior->next = p->next;
p->next->prior = p->prior;        
free(p);
  • 1
  • 2
  • 3
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/寸_铁/article/detail/801641
推荐阅读
相关标签
  

闽ICP备14008679号