线性表知识点总结

线性表的逻辑结构

  • 定义:线性表是具有相同数据类型的n(n≥0)个数据元素的有限序列。其中n为表长。当n=0时 线性表是一个空表
  • 特点:线性表中第一个元素称为表头元素;最后一个元素称为表尾元素。
    除第一个元素外,每个元素有且仅有一个直接前驱。
    除最后一个元素外,每个元素有且仅有一个直接后继。

线性表的顺序存储结构

  • 线性表的顺序存储又称为顺序表。
    它是用一组地址连续的存储单元(比如C语言里面的数组),依次存储线性表中的数据元素,从而使得逻
    辑上相邻的两个元素在物理位置上也相邻。

  • 建立顺序表的三个属性:
    1.存储空间的起始位置(数组名data)
    2.顺序表最大存储容量(MaxSize)
    3.顺序表当前的长度(length)

#define Maxsize 50  //定义线性表的最大长度 
typedef int Elemtype  // 假定表中的元素类型是整型 
typedef struct{
    Elemtype data [maxsize];  // 顺序表中的元素
    int lengh;  // 顺序表的类型定义
}Sqlist;
  • 其实数组还可以动态分配空间,存储数组的空间是在程序执行过程中通过动态存储分配语句分配
typedef int Elemtype  // 假定表中的元素类型是整型 
typedef struct{
	Elemtype *data;  // 指示动态分配数组的指针
	int Maxsize,lengh;  // 数组的最大容量和当前个数
}Sqlist;

//C语言的动态分配语句为
#define InitSize 100
SeqList L;
L.data = (ElemType*)malloc(sizeof(ElemType)*InitSize);
/*
注意:动态分配并不是链式存储,同样还属于顺序存储结构,
只是分配的空间大小可以在运行时决定
*/
  • 总结:
    • 1.顺序表最主要的特点是随机访问(C语言中基于数组),即通过首地址和元素序号可以在O(1)的时间内找到指定的元素。
    • 2.顺序表的存储密度高,每个结点只存储数据元素。无需给表中元素花费空间建立它们之间的逻辑关系(因为物理位置相邻特性决定)
    • 3.顺序表逻辑上相邻的元素物理上也相邻,所以插入和删除操作需要移动大量元素。

顺序表的操作

  • 1.插入

    • 算法思路:
      • 1.判断i的值是否正确
      • 2.判断表长是否超过数组长度
      • 3.从后向前到第i个位置,分别将这些元素都向后移动一位
      • 4.将该元素插入位置i 并修改表长
    • 代码
bool ListInsert(SqList &L,int i, ElemType e){  //形参:顺序表,插入位置,插入的数
      if(i<1 || i>L.length +1)
            return false;
      if(L.length>=MaxSize)
            return false;
      for(int j =L.lenth;j>i;j--)
            L.data[j] = L.data[j-1];
      L.data[i-1] = e;
      L.length++;
      return true;
}
  • 分析:

    • 最好情况:在表尾插入(即i=n+1),元素后移语句将不执行,时间复杂度为O(1)。
    • 最坏情况:在表头插入(即i=1),元素后移语句将执行
      n次,时间复杂度为O(n)。
    • 平均情况:假设pi(pi=1/(n+1) )是在第i个位置上插入
      一个结点的概率,则在长度为n的线性表中插入一个结
      点时所需移动结点的平均次数为

  • 2.删除

    • 算法思路:
      • 1.判断i的值是否正确
      • 2.取删除的元素
      • 3.将被删元素后面的所有元素都依次向前移动一位
      • 4.修改表长
    • 代码
bool ListDelete(SqList &L,int i, ElemType &e){
      if(i<1 || i>L.length)
            return false;
      e = L.data[i-1]
      for(int j=i;j<L.length;j++)
            L.data[j-1] = L.data[j];
      L.length--;
      return true;
}
  • 分析

    • 最好情况:删除表尾元素(即i=n),无须移动元素,时间复杂度为O(1)。
    • 最坏情况:删除表头元素(即i=1),需要移动除第一个元素外的所有元素,时间复杂度为O(n)。
    • 平均情况:假设pi(pi=1/n)是删除第i个位置上结点的概率,则在长度为n的线性表中删除一个结点时所需移动结点的平均次数为

线性表的链式存储结构

  • 线性表的链式存储是指通过一组任意的存储单元来存储线性表中的数据元素。
typedef struct LNode{  // 定义单链表节点类型
	Elemtype data;  // 数据域
	struct LNode *next;  // 指针域
}LNode,*LinkList;
// 因为每个节点只有一个指针指向下一个结点,故又称单链表
  • 头结点和头指针的区别?
    • 不管带不带头结点,头指针始终指向链表的第一个结点,而头结点是带头结点链表中的第一个结点,结点内通常不存储信息
  • 为什么要设置头结点?
    • 1.处理操作起来方便 例如:对在第一元素结点前插入结点和删除第一结点起操作与其它结点的操作就统一了
    • 2.无论链表是否为空,其头指针是指向头结点的非空指针,因此空表和非空表的处理也就统一了。

单链表的操作

  • 1.头插法建立单链表:

    • 建立新的结点分配内存空间,将新结点插入到当前链表的表头
    • 代码

LinkList CreatList(LinkList &L){
	LNode *s;  // 辅助指针
	int x;
	L = (LinkList)malloc(sizeof(LNode));  // 创建头结点
	L->next = Null;  // 创建为空链表
	scanf("%d",&x)
	while(x!=999){
		s=(LNode*)malloc(sizeof(LNode));  // 创建新结点
		s->data = x;
             	s->next = L->next;
		L->next = s;  // 将新结点插入表中
		scanf("%d",&x);	 // 读入下一个结点值
	}
	return L;
}
  • 2.尾插法建立单链表:

    • 建立新的结点分配内存空间,将新结点插入到当前链表的表尾
    • 代码

LinkList CreatList(LinkList &L){
	int x;
	L = (LinkList)malloc(sizeof(LNode));  // 创建头结点
	LNode *s,*r=L;  // 辅助指针,r为表尾指针,指向表尾
	scanf("%d",&x)
	while(x!=999){
		s=(LNode*)malloc(sizeof(LNode));  // 创建新结点
		s->data = x;
		r->next = s;
                r=s;  // r指针指向新的表尾结点
		scanf("%d",&x);	 // 读入下一个结点值
	}
	r->next = Null; //表尾指针指空
	return L;
}
  • 3.按序号查找结点

    • 在单链表中从第一个结点出发,顺指针next域逐个往下搜索,直到找到第i个结点为止,否则返回最后一个结点指针域NULL。
    • 代码
LNode *GetElem(LinkList l,int i){  //创造结构体指针,一个指针直接遍历链表
	int j = 1;
	LNode *p = L->next;
	if(i==0) return L;
	if(i<1) return Null;
	while(p&&j<1){
		p = p->next;
		j++;
	}
	return p;
}
  • 4.按值查找结点

    • 从单链表第一个结点开始,由前往后依次比较表中各结点数据域的值,若某结点数据域的值等于给定值e,则返回该结点的指针;若整个单链表中没有这样的结点,则返回NULL。
    • 代码
LNode *LocateElem(LinkList L,ElemType e){  //创造结构体指针,一个指针直接遍历链表
	LNode *p = L->next;
	while(p!=Null&&p->data!=e)
		p = p->next;
	return p;
}
  • 5.插入

    • 插入操作是将值为x的新结点插入到单链表的第i个位置上。先检查插入位置的合法性,然后找到待插入位置的前驱结点,即第i−1个结点,再在其后插入新结点。

    • 算法思路:

      1.取指向插入位置的前驱结点的指针
      ① p=GetElem(L,i-1);
      2.令新结点s的指针域指向p的后继结点
      ② s->next=p->next;
      3.令结点p的指针域指向新插入的结点s
      ③ p->next=s;

  • 6.删除

    • 删除操作是将单链表的第i个结点删除。先检查删除位置的合法性,然后查找表中第i−1个结点,即被删结点的前驱结点,再将其删除。

    • 算法思路:

      1.取指向删除位置的前驱结点的指针 p=GetElem(L,i-1);
      2.取指向删除位置的指针 q=p->next;
      3.p指向结点的后继指向被删除结点的后继 p->next=q->next
      4.释放删除结点 free(q);

双链表

  • 定义
typedef struct LNode{  // 定义单链表结点类型
	ElemType data;  // 数据域
	struct LNode *next;  // 指针域
}LNode,*LinkList;

typedef struct DNode{  // 定义双链表结点类型
	ElemType data;  // 数据域
	struct DNode *prior,*next;  // 前驱和后继指针
}DNode,*DinkList;

  • 1.插入:(方法不唯一)

    ① s->next=p->next;
    ② p->next->prior=s;
    ③ s->prior=p;
    ④ p->next=s;

  • 2.删除:

    ① p->next=q->next;
    ② q->next->prior=p;
    ③ free(q);

循环链表&&静态链表

  • 循环单链表:循环单链表和单链表的区别在于,表中最后一个结点的指针不是NULL,而改为指向头结点,从而整个链表形成一个环

  • 循环双链表:类比循环单链表,循环双链表链表区别于双链表就是首尾结点构成环

    • 当循环双链表为空表时,其头结点的prior域和next域都等于Head。

  • 静态链表:静态链表是用数组来描述线性表的链式存储结构。

    定义:

#define MaxSize 50
typedef int ElemType
typedef struct{
	ElemType data;
	int next;
}SLinkList[MaxSize]
  • 数组第一个元素不存储数据,它的指针域存储第一个元素所在的数组下标。链表最后一个元素的指针域值为-1。
  • 例子:

image-20200825193629817

原文地址:https://www.cnblogs.com/Lance-WJ/p/13561775.html