《数据结构与算法》课程笔记 第二章 2.1 线性表

1. 线性表

1 线性表的基本概念

线性表是最基本和最常用的一类数据结构,它表示的是线性结构。在线性结构中,数据元素之间存在着一对一的关系,其特点是数据元素之间按某种规定存在一个顺序关系。

  • a_{i} 的数据类型相同。
  • 位序 i 从 1 开始。
  • 唯一前驱和唯一后继。 

2 线性表的基本操作: 

线性表定义为指针类型 SqListPtr,数据元素类型为 ElemType。

2.1 初始化和销毁操作

2.2 引用型操作 

2.3 加工型操作

扫描二维码关注公众号,回复: 6220990 查看本文章
  • 函数参数是指针类型的是希望将得到的结果存入该指针。 

2.4 线性表的基本操作测试函数

3 基于线性表操作的简单应用

3.1 合并线性表

Status List_Union(SqListPtr La, SqListPtr Lb)
{
	ElemType elem;          //存放从 Lb 中取出的元素
	Status status;          //状态代码
	int i,j;
	int len = List_Size(Lb);  //len 存放 Lb 的元素个数
	for(i=1;i<=len;i++)
	{
		List_Retrieve(Lb,i,&elem);  //取出 Lb 中第 i 个数据元素
		status = List_Locate(La,elem,&j);  //判断它是否在 La 中
		if (status!=success) //如果不在
		{
			status = List_Insert(La,1,elem); //插入到第一个位置
			if(status!=success)
				break;                       //插入失败则退出
		}
		else List_Add(La,j,1);          //La 的第 j 个数据加1
	}
	return status;
}

  • m 和 n 同数量级,则时间复杂度为 O(N^{2}) 。选元素个数小的作为 Lb。

3.2 合并2个有序表

  • T(N)=O(N+M) 。如果 n 和 m 同数量级,则时间复杂度为 O(N)
Status List_Merge(SqListPtr La, SqListPtr Lb, SqListPtr Lc)
{
	ElemType elem1, elem2;
	status = List_Init(Lc);
	int i = 1, j = 1, k = 1;  //i,j,k分别用于指示 La,Lb,Lc中当前元素
	int n = List_Size(La);
	int m = List_Size(Lb);
	while(i<=n && j<=m)//两个表都还未处理完
	{
		List_Retrieve(La,i,&elem1);
		List_Retrieve(Lb,j,&elem2);
		if (elem1 < elem2)
		{
			status = List_Insert(Lc,k,elem1);
			i = i+1;
		}
		else
		{
			status = List_Insert(Lc,k,elem2);
			j = j+1;
		}
		k = k+1;
	}
	while(i <= n)//表La还未处理完
	{
		List_Retrieve(La,i,&elem1);
		status = List_Insert(Lc,k,elem1);
		i = i+1;
		k = k+1;
	}
	while(j <= m)//表Lb还未处理完
	{
		List_Retrieve(Lb,j,&elem2);
		status = List_Insert(Lc,k,elem2);
		j = j+1;
		k = k+1;
	}
	return status;
}

4 线性表的存储结构 

4.1 线性表的顺序存储结构

用一组地址连续的存储单元依次存放线性表中的数据元素。 

  • 线性表的起始地址 s 称作线性表的基地址 
  • 可以随机存取数据。如:数组

顺序存储结构的实现:

#define LIST_INIT_SIZE 100
#define LIST_INCREAMENT 10

enum Status
{
	success,fail,fatal,range_error   //状态值枚举
};

typedef int ElemType;

typedef struct SqList
{
	ElemType *elem; //存储空间的首地址指针
	int length;     //线性表的长度
	int list_size;  //线性表空间大小
}SqList, *Ptr;
typedef Ptr SqlListPtr;

基本操作的实现:初始化、创建线性表   (时间复杂度:O(1))

Status LIST_INIT(SqlListPtr L)
{
	Status s = success;
	L->list_size = LIST_INIT_SIZE;
	L->length = 0;
	L->elem = (ElemType *)malloc(sizeof(ElemType)*L->list_size);
	if(L->elem == NULL)
		s = fatal;
	return s;
}

基本操作的实现:查找   

  • 查找--按位置  (时间复杂度:O(1)) 
Status List_Retrieve(SqlListPtr L, int pos, ElemType *elem)
{
	Status s = range_error;
	if (L)
	{
		if ((pos-1)>=0 && (pos-1) < L->length)
		{
			*elem = L->elem[pos-1];
			s = success;
		}
	}
	else
		s = fatal;
	return s;
}
  • 查找--按值 (时间复杂度:O(N)) 
Status List_Locate(SqlListPtr L, ElemType elem, int *pos)
{
	Status s = range_error;
	if (L)
	{
		for(int i=0;i<L->length;++i)
		{
			if (L->elem[i] == elem)
			{
				*pos = i+1;    //存储的时候:0——length-1,但实际位置:1——length
				s = success;
				break;
			}
		}
	}
	else
		s = fatal;
	return s;
}

基本操作的实现:插入元素操作  (时间复杂度:O(N)) 

  • 第 i 个数据元素之前插入新数据元素 x 。 

  • 需要从后往前赋值,如果从前往后赋值,则后一个值会被前一个值覆盖掉,导致后面的值都为 x 。 

Status List_Insert(SqlListPtr L, int pos, ElemType elem)
{
	Status s = range_error;
	if ((pos-1)>=0 && (pos-1)<=L->length) //插入位置合法
	{
		if (L && L->length < L->list_size) //表未占满
		{
			for(int i=L->length-1;i>=pos-1;--i) //逻辑位置与实际存储位置的下标相差1
			{
				L->elem[i+1] = L->elem[i];
			}
			L->elem[pos-1] = elem;
			L->length++;
			s = success;
		}
	}
	else
		s = fail;
	return s;
}

 基本操作的实现:删除元素操作  (时间复杂度:O(N))

  • 从前往后循环赋值。若从后往前赋值,前面的元素会被后面的元素覆盖掉。 

Status List_delete(SqlListPtr L, int pos)
{
	Status s = range_error;
	if ((pos-1)>=0 && (pos-1)<L->length)
	{
		if(L && L->length>0)
		{
			for(int i=pos;i<L->length;++i)
			{
				L->elem[i-1] = L->elem[i];
			}
			L->length--;
			s = success;
		}
	}
	else 
		s = fail;
	return s;
}

基本操作的实现:其它操作实现

void List_Destory(SqlListPtr L)
{
	if(L)
	{
		if (L->elem)
		{
			free(L->elem);
            //void free(void *ptr);  
            //释放ptr指向的存储空间。被释放的空间通常被送入可用存储区池,以后可在调用malloc函数来再分配。  
			L->elem = NULL;
			L->length = 0;
		}
	}
}

void List_Clear(SqlListPtr L)
{
	if (L)
	{
		L->length = 0;
	}
}

bool List_Empty(SqlListPtr L)
{
	return (L->length == 0);
}

int List_Size(SqlListPtr L)
{
	return L->length;
}

Status List_Prior(SqlListPtr L, int pos, ElemType * elem)
{
	Status s = range_error;
	if (L)
	{
		if (pos-1 > 0 && pos-1 < L->length) //第一个位置没有前驱
		{
			*elem = L->elem[pos-2];  //pos位置的下标为pos-1,前驱的下标为pos-2
			s = success;
		}
	}
	return s;
}

Status List_Next(SqlListPtr L, int pos, ElemType *elem)
{
	Status s = range_error;
	if (L)
	{
		if (pos-1>=0&&pos-1<L->length-1)//最后一个位置没有后继
		{
			*elem = L->elem[pos]; //pos位置的下标为pos-1,后继的下标为pos
			s = success;
		}
	}
	return s;
}

顺序存储结构存在的问题:

  •  无法知道到底预先分配多大空间合适。
  • 插入、删除开销很大。

解决方案:地址不连续(链式存储结构)

  •  一个数据一个空间,数据之间不连续,避免一次性大空间分配失败,或者使用过程中空间不足的问题。
  • 每个空间需要两部分:一部分存储数据;一部分存储数据之间的关系。(用C语言中的结构实现)

4.2 线性表的链式存储结构 

4.2.1 单链表

用一组地址任意的存储单元存放线性表中的数据元素。 

数据域(数据元素)+ 指针域(指示后继元素存储位置)= 结点

以“结点的序列”表示线性表——称作链表。

  • 线性表的顺序存储结构在访问元素时可以一步到位,而链式存储结构在访问元素时需要从头指针开始一次访问每个元素,时间复杂度为:O(N)。

单链表类型定义: 

typedef struct Node
{
	ElemType elem;
	struct Node *next;
}Node,*Ptr;
typedef Ptr *SqListPtr;

单链表基本操作实现:查找--按位置查找 

  • 问题:在给定的带头结点的单链表L中,查找指定位置的数据元素,如果存在,则返回success,同时取回相应结点的数据。
  • 方法:链表的操作只能通过从头指针出发,顺着链域 next 逐个结点比较,直到搜索到指定位置的结点为止。
Status List_Retrieve(SqListPtr L, int pos, ElemType *elem)
{
	Status s = range_error;
	Ptr p = (*L)->next; //带头结点,移动 p 指向第一个元素结点
	int i = 1; //计数器。置初值
	while(p && i<pos) //p 指向的结点存在,且未到达指定位置。条件1:防止pos>表长。条件2:控制取第pos个,防pos<1
	{
		i++;
		p = p->next;
	}
	if (p && i==pos)
	{
		*elem = p->data;
		s = success;
	}
	return s;
}

单链表基本操作实现:查找--按值查找  (时间复杂度:O(N))

Status List_Locate(SqListPtr L, ElemType elem, int *pos)
{
	Status s = range_error;
	Ptr p = (*L)->next;  //带头结点
	int i = 1;
	while(p!=NULL)
	{
		if(p->data == elem)
			break;
		i++;
		p = p->next;
	}
	/*while(p && p->data!=elem)
	{
		i++;
		p = p->next;
	}*/
	if (p)
	{
		*pos = i;
		s = success;
	}
	return s;
}

 单链表基本操作实现:插入操作(时间复杂度:O(N)(花在定位上))

  • 挂 S 结点在单链表指定位置的操作: 
s->next = p->next;
p->next = s;
//若用下面的形式,将导致链表后面断开,无法连接。(所以先要定位到ai位置)
p->next = s;
s->next = p->next;
Status List_Insert(SqListPtr L, int pos, ElemType elem)
{
	Status status;
	Ptr p,s;
	status = List_Retrieve(L,pos-1,&p);
	if (status == success)
	{
		s = (LinkedPtr)malloc(sizeof(Node)); //分配一个新的结点
		if (s)
		{
			s->data = elem;
			s->next = p->next;
			p->next = s;
			status = success;
		}
		else
			status = fatal;
	}
	else
		status = range_error;
	return status;
}
  • 虽然时间复杂度都是O(N),但是顺序存储结构需要大量移动数据。而链式存储结构只需修改指针内容,不需移动数据元素,操作方便。

 单链表基本操作实现:删除操作   (时间复杂度:O(N)(花在定位上))

Status List_delete(SqListPtr L, int pos)
{
	Status status = fail;
	Ptr s, p;
	status = List_Retrieve(L, pos-1, &p);  //定位到 pos-1 位置
	if (status == success)
	{
		s = p->next;   //将要删除的位置pos的内存空间赋值给 s 。
		p->next = s->next;  // 修改指针内容(i-1 的后继为 i+1 的地址)
		free(s);
		s = NULL;     //释放 pos 位置的内存空间
		status = success;
	}
	return status;
}

 单链表基本操作实现:创建单链表(创建单链表时在头结点插入效率最高)(时间复杂度:O(N))

Status List_Create(SqListPtr L, ElemType data[], int len)
{
	Status s;
	Ptr p;
	s = List_Init(L);
	if (s == success)
	{
		for(int i=len-1;i>=0;--i)
		{
			p = (Ptr)malloc(sizeof(Node));  //分配内存空间,创建结点
			if (p)
			{
				p->data = data[i];    //把数据元素放入结点 p 。
				p->next = (*L)->next;
				(*L)->next = p;
			}
			else
			{
				s = fail;
				break;
			}
		}
	}
	return s;
}

猜你喜欢

转载自blog.csdn.net/sinat_35483329/article/details/85704783