数据结构-2.线性表

线性表

定义 & 逻辑结构

定义

相同特性数据元素的有限序列。

  • 特点
    • 有穷 ( 元素个数有限)
    • 一致 (元素性质相同,数据类型相同)
    • 序列 (元素相对位置线性有序,位置取决于它的序号)
  • 二元组:L = ( D , R )
D = {ai | 1 <= i <= n ,n>0}
R = {r}
r = {<ai, ai+1>  | 1 <= i <= n-1}

抽象数据类型描述

ADT List
{	数据对象:
		D = {ai | 1 <= i <= n ,n>0}
	数据关系:
		R = {<ai, ai+1> | ai,ai+1 ∈D,1 <= i <= n-1}
	基本运算:
		InitList(&L):				初始化,构造新的线性表
		DestroyList(&L):			销毁线性表,释放L占用空间
		ListEmpty(L):				判断线性表是否为空	
		ListLength(L):				求线性表长度
		DispList(L):				输出线性表
		GetElem(L,i,&e):			求线性表中某个数据元素值
		LocateElem(L,e):			查找元素
		ListInsert(&L,i,e):			插入元素
		ListDelete(&L,i,&e):		删除元素
}

线性表作用

  1. 直接存放数据
  2. 使用基本操作完成更复杂的操作

线性表的顺序存储结构

顺序存储结构就是把线性表中所有元素按其逻辑顺序依次存储到一块连续的存储空间中。

简称 顺序表。C\C++ 中借助数组实现。数组大小 >= 线性表元素个数,

但不是任何数组都是线性表(运算不同)。

顺序表实现

结构体中数组的类型决定了顺序表中数据元素的类型,这里以int 类型为例

// 声明线性表的顺序存储结构

#define MaxSize 50   	// 数组大小

typedef struct
{
    int data[MaxSize];	// 存放线性表的元素	
    int length;			// 存放线性表的长度		
}SqList;				// 顺序表类型

data : 存储所有数据元素

length : 线性表的实际长度

SqList : 顺序表的类型

顺序表基本运算实现

#include <stdio.h>
#include <stdlib.h>
#define MaxSize 50   // 数组大小

	// 声明线性表的顺序存储结构
	typedef struct
	{
		int data[MaxSize];	// 存放线性表的元素	
		int length;			// 存放线性表的长度
				
	}SqList;				// 顺序表类型

// 建立顺序表
void CreateList(SqList*& L, int a[], int n)
{
	int i = 0, k = 0;
	L = (SqList *)malloc(sizeof(SqList));
	while (i < n)
	{
		L->data[k] = a[i];
		k++; i++;
	}
	L->length = k;
}

// 初始化线性表 
void InitList(SqList*& L)
{
	L = (SqList*)malloc(sizeof(SqList));
	L->length = 0;
}

// 销毁线性表
void DestroyList(SqList*& L)
{
	free(L);		//释放L 顺序表空间
}

// 判断线性表是否为空
bool ListEmpty(SqList* L)
{
	return (L->length == 0);
}

// 求线性表的长度
int ListLength(SqList* L)
{
	return (L->length);
}

// 输出线性表
void DispList(SqList* L)
{
	for (int i = 0; i < L->length; i++)
	{
		printf("%d ", L->data[i]);
	}
	printf("\n");
}

// 求线性表中某个元素值
bool GetElem(SqList* L, int i, int& e)
{
	if (i<1 || i>L->length)
	{
		return false;
	}
	e = L->data[i - 1];
	return true;
}

// 按元素值查找
int LocateElem(SqList* L, int e)
{
	int i = 0;
	while (i<L->length && L->data[i]!=e)
		i++;
	if (i>=L->length)
		return 0;	
	else
		return i + 1;
	
}

// 插入数据元素
bool ListInsert(SqList* &L, int i, int e)
{
	int j;
	if (i<1 || i>L->length || L->length == MaxSize)
		return false;
	i--;
	for (j = L->length; j > i; j--)
		L->data[j] = L->data[j - 1];
	L->data[i] = e;
	L->length++;
	return true;
}

// 删除元素
bool ListDelete(SqList*& L, int i, int& e)
{
	int j;
	if (i<1 || i>L->length)
		return false;
	i--;
	e = L->data[i];
	for (j = i; j < L->length; j++)
		L->data[j] = L->data[j + 1];
	L->length--;
	return true;
}

案例

// 删除指定的所有元素的2 种解法 ,时间复杂度 n  空间 1
void delnodel1(SqList*& L, int n)
{
	int k = 0, i;
	for (i = 0; i < L->length; i++)
	{
		if (L->data[i] != n)
		{
			L->data[k] = L->data[i];
			k++;
		}
	}
	L->length = k;
}

void delnodel2(SqList*& L, int n)
{
	int k = 0, i = 0;
	while (i < L->length)
	{
		if (L->data[i] == n)
			k++;
		else
			L->data[i - k] = L->data[i];
		i++;
	}
	L->length -= k;
}

// 一组数中以第一个数为基准 比它大的放右边,比他小的放左边
void partitional1(SqList*& L)
{
	int i = 0, j = L->length - 1;
	int pivot = L->data[0];
	while (i < j)
	{
		while (i < j && L->data[j] >pivot)
			j--;
		while (i < j && L->data[i] <= pivot)
			i++;
		if (i < j)
		{
			int temp = L->data[j];
			L->data[j] = L->data[i];
			L->data[i] = temp;
		}
	}
	int temp = L->data[0];
	L->data[0] = L->data[i];
	L->data[i] = temp;
}

void partitional2(SqList*& L)
{
	int i = 0, j = L->length - 1;
	int pivot = L->data[0];
	while (i < j)
	{
		while (i < j && L->data[j] >pivot)
			j--;
		L->data[i] = L->data[j];
		while (i < j && L->data[i] <= pivot)
			i++;
		L->data[j] = L->data[i];

	}
	L->data[i] = pivot;
}

// 将所有奇数移动到偶数之前的2 种解法
void moveJiTofirst1(SqList*& L)
{
	int i = 0, j = L->length - 1;

	while (i < j)
	{
		while (i < j && L->data[j] % 2 == 0)
			j--;
		while (i < j && L->data[i] % 2 == 1)
			i++;
		if (i < j)
		{
			int temp = L->data[i];
			L->data[i] = L->data[j];
			L->data[j] = temp;
		}
	}
}

void moveJiTofirst2(SqList*& L)
{
	int i = -1, j;
	for (j = 0; j < L->length - 1; j++)
		if (L->data[j] % 2 == 1)
		{
			i++;
			if (i != j)
			{
				int temp = L->data[i];
				L->data[i] = L->data[j];
				L->data[j] = temp;
			}
		}
}
  • 线性表中逻辑序号是以 1 开始,而 顺序表中的data 数组下标为 0 开始,注意转换
  • 可以直接 使用顺序表Q 或者顺序表指针 L 来提供一个顺序表,使用顺序表指针方便释放算法设计,节省为形参分配的内存
  • SqList *L | SqList Q
  • L -> length | Q.length 不同的提供方法 引用length 域 的方法不同

线性表的链式存储结构

线性表的链式存储成为 链表

存储对象不仅包括数据本身,还包括元素之间的逻辑关系,C\C++ 中使用指针域来实现。

方法 是在每个结点中除包含数据域外还添加指针域,用于指向其它结点。

单链表:外添1 个指针域,指向下一个结点。

双链表:外添2 个指针域,分别指向前驱、后驱结点。

  • 通常每个链表都带一个头结点,通过头结点的指针唯一标识该链表。称为 头指针
  • 指向开始结点的指针 == 首指针 、指向尾结点的指针 == 尾指针
image-20220317194047930

存储密度 = 结点中数据占用存储量 / 结点所占存储量 (越高空间利用越好)

单链表

// 每个结点类型用 LinkNode 表示  ,声明如下
typedef struct LNode
{
	ElemType data;			// 数据
	struct LNode * next;	// 指针域指向后继结点
}LinkNode;					// 单链表结点类型
  • 单链表中增加头结点的优点:
    1. 使首结点的删除和插入操作与其他结点一致,无需特殊操作。
    2. 统一空表和非空表的处理过程(无论空表非空表都有头结点)。
基本操作
  1. 插入结点操作
s->next = p->next;
p->next = s;
  1. 删除结点
p -> next = p -> next -> next
// 通常需要释放被删除的结点 空间
q = p -> next;
p -> next = q -> next;
free(p);

删除和插入一个结点都需要先找到它的前驱结点

  1. 头插法 建立单链表
// 头插法建立单链表
void CreateListF(LinkNode*& L, int a[], int n)
{
	// 创建头结点
	LinkNode* s;
	L = (LinkNode*)malloc(sizeof(LinkNode));
	L->next = NULL;
	// 读取数组并创建结点
	for (int i = 0; i < n; i++)
	{
		s = (LinkNode*)malloc(sizeof(LinkNode));
		s->data = a[i];			// 给 s 赋值
		s->next = L->next;		// 新结点连接到之前的新结点
		L->next = s;			// 头结点 指向新结点
	}
}
  1. 尾插法 建立单链表
// 尾插法 建立单链表
void CreateListR(LinkNode*& L, int a[], int n)
{
	// 建立头结点,声明尾结点和新增节点
	LinkNode* r, * s;
	L = (LinkNode*)malloc(sizeof(LinkNode));
	r = L;							// 尾指针初始指向头指针

	for (int i = 0; i < n; i++)
	{
		s = (LinkNode*)malloc(sizeof(LinkNode));	// 为新增结点开辟空间
		s->data = a[i];
		r->next = s;		// 当前尾结点指向新增结点
		r = s;				// 尾结点永远为最后一个新增的结点
	}
	r->next = NULL;			// 尾结点 指针域为空
}
基本算法
// 单链表
typedef struct LNode
{
	int data;
	struct LNode* next;
}LinkNode;

// 头插法建立单链表
void CreateListF(LinkNode*& L, int a[], int n)
{
	// 创建头结点
	LinkNode* s;
	L = (LinkNode*)malloc(sizeof(LinkNode));
	L->next = NULL;
	// 读取数组并创建结点
	for (int i = 0; i < n; i++)
	{
		s = (LinkNode*)malloc(sizeof(LinkNode));
		s->data = a[i];			// 给 s 赋值
		s->next = L->next;		// 新结点连接到之前的新结点
		L->next = s;			// 头结点 指向新结点
	}
}

// 尾插法 建立单链表
void CreateListR(LinkNode*& L, int a[], int n)
{
	// 建立头结点,声明尾结点和新增节点
	LinkNode* r, * s;
	L = (LinkNode*)malloc(sizeof(LinkNode));
	r = L;							// 尾指针初始指向头指针

	for (int i = 0; i < n; i++)
	{
		s = (LinkNode*)malloc(sizeof(LinkNode));	// 为新增结点开辟空间
		s->data = a[i];
		r->next = s;		// 当前尾结点指向新增结点
		r = s;				// 尾结点永远为最后一个新增的结点
	}
	r->next = NULL;			// 尾结点 指针域为空
}


// 初始化
void InitList(LinkNode*& L)
{
	// 创建头结点,指针域为空
	L = (LinkNode*)malloc(sizeof(LinkNode));
	L->next = NULL;

}

// 销毁
void DestroyList(LinkNode*& L)
{
	// 逐一释放全部结点  pre: 当前头结点  P: 头结点的下一个结点 
	LinkNode* pre = L, * p = pre->next;
	while (p!=NULL)			// 如果头结点还有下一个结点 则释放头结点并 依次将下一个结点 设置为头结点
	{
		free(pre);
		pre = p;
		p = p->next;
	}
	free(pre);		// 头结点没有下一个结点了,直接释放头结点
}


// 判断空表
bool ListEmpty(LinkNode* L)
{
	return (L->next == NULL);
}

// 长度
int ListLength(LinkNode* L)
{
	int k = 0;
	LinkNode* p = L;
	while (p->next != NULL)		// 如果它的指针域不为空 则说明他还有下一个结点
	{
		k++;
		p = p->next;
	}
	return k;
}

// 输出
void DispList(LinkNode* L)
{
	LinkNode* p = L;
	while (p->next != NULL)
	{
		printf("%d", p->data);
		p = p->next;
	}
	printf("\n");
}

// 求某个数据的元素值
bool GetElem(LinkNode* L, int i, int &e)
{
	int j = 1;					// 记录当前链表位置
	LinkNode* p = L->next;		// 指向第一个结点
	if (i < 0)
		return false;			// 正数位置

	while (p != NULL && j < i)	// 遍历到该位置,排除 空结点(不存在指定结点)
	{
		p = p->next;
		j++;
	}

	if (p == NULL)				// 如果因为p为空终止循环 则没有这么多结点,获取失败,否则获取成功
		return false;
	else
	{
		e = p->data;
		return true;
	}
}

// 按元素值查找
int LocateElem(LinkNode* L, int e)
{
	int i = 1;
	LinkNode* p = L->next;
	while (p != NULL && p->data!=e)
	{
		p = p->next;
		i++;
	}
	if (p == NULL)
		return 0;
	else
		return i;
}

// 插入数据元素
bool ListInsert(LinkNode*& L, int i, int e)
{
	// 遍历到逻辑序号 i-1 的位置
	int j = 0;
	LinkNode* p = L,*s;
	if (i <= 0)
		return false;

	while (p!=NULL && j < i-1)
	{
		p = p->next;
		j++;
	}

	// 如果能够找到 则执行插入,否则返回false
	if (p == NULL)
		return false;
	else
	{
		s = (LinkNode*)malloc(sizeof(LinkNode));
		s->data = e;
		s->next = p->next;
		p->next = s;
		return true;
	}
}

// 删除 数据元素
bool ListDelete(LinkNode*& L, int i, int& e)
{
	// 遍历到逻辑序号 i-1 的位置
	int j = 0;
	LinkNode* p = L,* q;
	if (i <= 0)
		return false;

	while (p != NULL && j < i - 1)
	{
		p = p->next;
		j++;
	}

	// 如果能够找到 则执行删除,否则返回false
	if (p == NULL)
		return false;
	else
	{
		q = p->next;
		if (p == NULL)
			return false;		// 只是找到了需要删除位置的前一个位置,并不知道需要删除的位置是否为空
		e = q->data;			// 返回被删除数据
		p->next = q->next;		
		free(q);				// 改变指向 并释放空间
		return true;
	}

}
案例
// 单链表拆分为两个链表
// 自己的算法
void split(LinkNode* &L, LinkNode*& L2)
{
	LinkNode* p = L->next, *p2 = L2, *q;

	while (p!=NULL)
	{
		if (p->next != NULL)
		{
			q = p->next;
			p->next = q->next;
			q->next = p2->next;
			p2->next = q;
			q = p2;
			p = p->next;
		}
	}
}
// 书上的算法
void split2(LinkNode*& L, LinkNode*& L1, LinkNode* & L2)
{
	// 分别利用头插法和尾插法 建立 L1  L2
	LinkNode* p = L->next, * q , * rl;
	L1 = L;
	rl = L;

	// 初始化L2
	L2 = (LinkNode*)malloc(sizeof(LinkNode));		// L2 的头结点
	L2->next = NULL;

	while (p != NULL)
	{
		// 尾插法将 p 插入L1
		rl->next = p;
		rl = p;

		// 头插法 插入 L2
		p = p->next;
		q = p->next;		// 保存 p 的后继结点 
		p->next = L2->next;
		L2->next = p;
		p = q;				// 将原本 p 的后继结点返回给 p
	}
	rl->next = NULL;
}

// 删除 最大的结点
// 自己的算法

void DelMaxNode(LinkNode*& L, int &e)
{
	LinkNode* p, * pre, * maxpre, * q;
	p = L->next;
	pre = L;
	maxpre = L;
	while (p != NULL)
	{
		if ((p->data) > (maxpre->next->data))
			maxpre = pre;

		p = p->next;
		pre = pre->next;
	}

	e = maxpre->next->data;
	q = maxpre->next;
	pre->next = q->next;
	free(q);
}

// 书上的算法
void DelMaxNode2(LinkNode*& L, int& e)
{
	LinkNode* p = L->next, * pre = L, * maxp = p, * maxpre = pre;
	while (p != NULL)
	{
		if (maxp->data < p->data)
		{
			maxp = p;
			maxpre = pre;
		}
		pre = p;
		p = p->next;
	}
	maxpre->next = maxp->next;
	e = maxp->data;
	free(maxp);
}


// 使其元素 有序递增排列
// 书上的算法
void sort(LinkNode*& L)
{

	LinkNode* p, * pre, * q;
	p = L->next->next;			// 保存第二个结点
	L->next->next = NULL;

	while (p!=NULL)
	{
		q = p->next;			// 执行操作前先保存下一个 要操作的结点
		pre = L;				// 从有序单链表 表头开始比较,pre 指向插入节点的前驱结点
		while (pre->next != NULL && pre->next->data < p->data)		// 为待插入结点 找到合适的位置
			pre = pre->next;	
		p->next = pre->next;		// 插入结点
		pre->next = p;
		p = q;					// 将下一个结点位置交给 p
	}
}

双链表

// 双链表 的类型声明
typedef struct DNode
{
    ElemType data;			// 数据元素
    struct DNode *prior;	// 前驱结点
    struct DNode *next;		// 后继结点
}DLinkNode;					// 双链表结点类型
  1. 头插法

    void CreateListF(DLinkNode*& L, int a[], int n)
    {
        // 建立初始化头结点 
        DLinkNode* s;
        L = (DLinkNode*)malloc(sizeof(DLinkNode));
        L->next = L->prior = NULL;
    
        for (int i = 0; i < n; i++)
        {
            // 新插入结点
            s = (DLinkNode*)malloc(sizeof(DLinkNode));
            s->data = a[i];
            s->next = L->next;
            if (L->next != NULL)
                L->next->prior = s;     // 如果L 后已有首结点,就需要修改L->next 的前驱结点为 s
            L->next = s;
            s->prior = L;
        }
    }
    
    
  2. 尾插法

    // 尾插法 建立双链表
    void CreateListR(DLinkNode*& L, int a[], int n)
    {
        // 建立初始化头结点 
        DLinkNode* s,*r;
        L = (DLinkNode*)malloc(sizeof(DLinkNode));
        // L->next = L->prior = NULL;
        r = L;
    
        for (int i = 0; i < n; i++)
        {
            s = (DLinkNode*)malloc(sizeof(DLinkNode));
            s->data = a[i];
            s->prior = r;
            r->next = s;
            r = s;
        }
        r->next = NULL;         // 最后一个元素next 为空
    }
    
基本算法
  • 双链表中 插入和删除结点的算法是不同与单链表的,其他都大概相同
  1. 插入结点
// 插入一个结点
s->next = p->next;
p->next->prior = s;
s->prior = p;
p->next = s; 		// 尽量把 p->next 操作放到后面执行
  1. 删除一个结点
q = p->next;
p->next = q->next;
q->next->prior = p;
  • 插入和删除实现
// 插入结点
bool InsertList(DLinkNode*& L, int i, int e)
{
    int j = 0;
    DLinkNode* p = L,*s;
    if (i <= 0)
        return false;

    while (i<i-1 && p!=NULL)
    {
        j++;
        p = p->next;
    }

    if (p == NULL)
        return false;
    
    else
    {
        s = (DLinkNode*)malloc(sizeof(DLinkNode));
        s->data = e;
        s->next = p->next;
        if(p->next!=NULL)
            p->next->prior = s;
        s->prior = p;
        p->next = s;
        return true;
    }
}

// 删除
bool DeleteList(DLinkNode*& L, int i, int &e)
{
    // 找到要删除结点的上一个结点
    // 定义需要使用的内部变量
    int j = 0;
    DLinkNode* p, *q;
    p = L;

    // 参数校验
    if (i <= 0)
        return false;
    
    // 寻找第 i-1 个结点
    while (j < i-1 && p != NULL)
    {
        j++;
        p = p->next;
    }

    // 检验是否找到 第 i-1 位
    if (p == NULL)
        return false;

    else
    {
        q = p->next;
        if (q == NULL)          // 检验是否有第i位
            return false;

        e = q->data;            // 返回删除值
        p->next = q->next;
        if(q->next!=NULL)
            q->next->prior = p;
        free(q);
        return true;
    }   
}
案例
// 链表逆置
// 自己的算法 : 挨个读取数据 再用头插法 建立新结点重新插入
void reverse(DLinkNode*& L)
{
    // 读出每个链表数据然后 头插法

    int e = 0;
    DLinkNode* p, *s;
    p = L->next;
    L->next = NULL;

    while (p != NULL)
    {
        e = p->data;

        // 头插法插入结点
        s = (DLinkNode*)malloc(sizeof(DLinkNode));
        s->data = e;
        s->next = L->next;
        if (L->next != NULL)
            L->next->prior = s;
        L->next = s;
        s->prior = L;
        p = p->next;
    }
}

// 书上的算法 
void reverse2(DLinkNode*& L)
{
    // 用原来的头结点构建一个空结点并保存原来的 结点链表
    DLinkNode* p=L->next, * q;
    L->next = NULL;

    while (p!=NULL)
    {
        // 头插法顺序 重新构建 达到逆序效果
        q = p->next;
        p->next = L->next;
        if (L->next != NULL)
            L->next->prior = p;
        L->next = p;
        p->prior = L;
        p = q;          // 获取下一个结点
    }
}
// 与书上的算法比较 自己的算法空间复杂度 更高,n 1


// 元素递增排列
// 自己的算法(同书上的算法)    利用原链表的头结点,创建一个 仅含头结点和一个元素结点的链表,再将剩下的元素一一与已插入的元素作比较
void sort(DLinkNode*& L)
{
    // p 还未插入元素   q 下一个元素,用于引用剩下的元素   pre 存放插入位置的前一个位置
    DLinkNode* p, *q, *pre ;

    p = L->next->next;
    L->next->next = NULL;

    while (p != NULL)
    {
        q = p->next;
        pre = L;            // 从头结点开始比较
        while (pre->next != NULL && pre->next->data < p->data)
            pre = pre->next;        // 待插入元素小于已插入元素当前位置元素,则继续向前比较
        // 遇到了大于等于待插入元素的已插入元素,开始插入结点
        p->next = pre->next;
        if (pre->next != NULL)          // 如果插入位置后面还有元素还要指定该元素的前驱元素为插入元素
            pre->next->prior = p;
        p->prior = pre;
        pre->next = p;
        p = q;              // 得到下一个需要插入的元素
    }
}

循环链表

  • 循环链表也有单双之分,经过改造即可成为循环 单、双链表。
  • 单链表 -> 循环单链表 : 尾结点->next = 头结点
  • 双链表-> 循环双链表 : 尾结点->next = 头结点 、头结点->prior = 尾结点
  1. 循环单链表中判断尾结点:p->next == L,双链表中 :L->prior
改造
  1. 改造单链表

    // 循环单链表 类型声明
    typedef struct LNode
    {
    	int data;
    	struct LNode* next;
    }CLinkNode;
    
    // 头插法建立 循环单链表
    void CreateListF(CLinkNode*& L, int a[], int n)
    {
    	// 创建头结点
    	CLinkNode* s;
    	L = (CLinkNode*)malloc(sizeof(CLinkNode));
    	L->next = L;		// 初始引用自己
    	// 读取数组并创建结点
    	for (int i = 0; i < n; i++)
    	{
    		s = (CLinkNode*)malloc(sizeof(CLinkNode));
    		s->data = a[i];			// 给 s 赋值
    		s->next = L->next;		// 新结点连接到之前的新结点
    		L->next = s;			// 头结点 指向新结点
    	}
    }
    
    // 尾插法 建立循环单链表
    void CreateListR(CLinkNode*& L, int a[], int n)
    {
    	// 建立头结点,声明尾结点和新增节点
    	CLinkNode* r, * s;
    	L = (CLinkNode*)malloc(sizeof(CLinkNode));
    	r = L;							// 尾指针初始指向头指针
    
    	for (int i = 0; i < n; i++)
    	{
    		s = (CLinkNode*)malloc(sizeof(CLinkNode));	// 为新增结点开辟空间
    		s->data = a[i];
    		r->next = s;		// 当前尾结点指向新增结点
    		r = s;				// 尾结点永远为最后一个新增的结点
    	}
    	r->next = L;			// 尾结点 指针域为L
    }
    
    
  2. 改造双链表

    // 循环双链表 的类型声明
    typedef struct DNode
    {
        int data;			// 数据元素
        struct DNode* prior;	// 前驱结点
        struct DNode* next;		// 后继结点
    }CDLinkNode;					// 双链表结点类型
    
    
    // 头插法建立循环双链表
    void CreateListF(CDLinkNode*& L, int a[], int n)
    {
        // 建立初始化头结点 
        CDLinkNode* s;
        L = (CDLinkNode*)malloc(sizeof(CDLinkNode));
        L->next = L;    
        L->prior = NULL;
    
        for (int i = 0; i < n; i++)
        {
            // 新插入结点
            s = (CDLinkNode*)malloc(sizeof(CDLinkNode));
            s->data = a[i];
            s->next = L->next;
            if (L->next != NULL)
                L->next->prior = s;     // 如果L 后已有首结点,就需要修改L->next 的前驱结点为 s
            else
                L->prior = s;           // 循环结点
    
            L->next = s;
            s->prior = L;
        }
    
    }
    
    // 尾插法 建立循环双链表
    void CreateListR(CDLinkNode*& L, int a[], int n)
    {
        // 建立初始化头结点 
        CDLinkNode* s, * r;
        L = (CDLinkNode*)malloc(sizeof(CDLinkNode));
        // L->next = L->prior = NULL;
        r = L;
    
        for (int i = 0; i < n; i++)
        {
            s = (CDLinkNode*)malloc(sizeof(CDLinkNode));
            s->data = a[i];
            s->prior = r;
            r->next = s;
            r = s;
        }
        r->next = L;         // 最后一个元素next 为空
        L->prior = r;
    }
    
    
案例
// 统计值 x 出现的次数( 单链表 )
int count(CLinkNode*& L, int x)
{
	int j = 0;
	CLinkNode* p = L->next;
	while (p!=L)
	{
		if (p->data == x)
			j++;
		p = p->next;
	}
	return j;
}

// 循环双链表 删除指定x
bool delelem(CDLinkNode*& L, int x)
{
    int i = 0;
    CDLinkNode* p = L->next;
    while (p != L && p->data != x)
        p = p->next;
    if (p != L)
    {
        p->next->prior = p->prior;
        p->prior->next = p->next;
        free(p);
        return true;
    }
    else
        return false;
}

// 判断一个循环双链表是否对称(值)
// 自己的算法
bool Symm(CDLinkNode* L)
{
    CDLinkNode* p=L->next, * q=L->prior;
    while (p!=q && p!=q->prior)
    {
        if (p->data != q->data)
            return false;
        p = p->next;
        q = q->prior;
    }
    return true;
}

// 书上的算法
bool Symm(CDLinkNode* L)
{
    bool same = true;
    CDLinkNode* p = L->next;
    CDLinkNode* q = L->prior;
    while (same)
    {
        if (p->data != q->data)
            same = false;
        else
        {
            if (p == q || p == q->prior)
                break;
            q = q->prior;
            p = p->next;
        }
    }
    return same;
}
// 我没有开辟变量,但我的每次循环都需要做多判断
// 书上开辟一个变量来控制循环,效率更高

线性表的应用(例题)

  • 两个表的自然连接
#include <stdio.h>
#include <stdlib.h>

// 任意两表的简单自然连接过程应用

// 步骤:
// 1. 问题描述
// 2. 设计数据类型
// 3. 设计运算算法
// 4. 设计程序完成功能

// 数据类型
// 每行作为一个数据结点,单链表
#define MaxCol 10

// 存放数据元素的结点
typedef struct Node1
{
	int data[MaxCol];
	struct Node1* next;
}DList;

// 单链表头结点
typedef struct Node2
{
	int Row, Col;
	DList* next;
}HList;

/// 算法设计
//交互方式建立单链表
void CreateTable(HList* & H)
{
	static int n = 1;
	int i,j;
	DList* r=NULL,*s;
	H = (HList*)malloc(sizeof(HList));
	H->next = NULL;
	printf("表 %d: \n", n);
	n++;
	printf("行,列:");
	scanf_s("%d%d", &H->Row, &H->Col);
	for (int i = 0; i < H->Row; i++)
	{
		s = (DList*)malloc(sizeof(DList));
		printf("第 %d 行:",i+1);
		for (j = 0; j < H->Col; j++)
			scanf_s("%d", &s->data[j]);

		// 链接
		if (H->next == NULL)
			H->next = s;
		else
			r->next = s;
		r = s;
	}
	r->next = NULL;
}

// 销毁单链表
void DestroyTable(HList* &H)
{
	DList* pre = H->next, *p = pre->next;
	while (p!=NULL)
	{
		free(pre);
		pre = p;
		p = p->next;
	}
	free(pre);
	free(H);
}

// 输出单链表
void DispTable(HList* &H)
{
	int j;
	DList* p = H->next;
	while (p!=NULL)
	{
		for (j = 0; j < H->Col; j++)
			printf("%4d", p->data[j]);
		printf("\n");
		p = p->next;
	}
}

// 自然连接
void LinkTable(HList*& H1, HList* &H2, HList* &H)
{
	int i, j;
	DList* p = H1->next, * q, * r = NULL, * s;
	printf("连接字段(1,2):");
	scanf_s("%d%d", &i, &j);

	H = (HList*)malloc(sizeof(HList));
	H->Row = 0;
	H->Col = H1->Col + H2->Col;
	H->next = NULL;

	while (p!=NULL)
	{
		q = H2->next;
		while (q != NULL)
		{
			if (p->data[i-1]==q->data[j-1])
			{
				s = (DList*)malloc(sizeof(DList));
				for (int k = 0; k < H1->Col; k++)
					s->data[k] = p->data[k];
				for (int k = 0; k < H2->Col; k++)
					s->data[k+H1->Col] = q->data[k];
				if (H->next == NULL)
					H->next = s;
				else
					r->next = s;
				r = s;

				H->Row++;
			}
			q = q->next;
		}
		p = p->next;
	}
	r->next = NULL;
}

// 求解程序
int main()
{
	HList* H,*H1,*H2;
	CreateTable(H1);
	CreateTable(H2);
	LinkTable(H1, H2, H);
	printf("连接表结果:\n");
	DispTable(H);
	
	DestroyTable(H1);
	DestroyTable(H2);
	DestroyTable(H);

	return 1;
}

image-20220320110111065

  • 求解实际问题步骤:

    1. 问题描述
    2. 设计数据类型
    3. 设计运算算法
    4. 设计程序完成功能
  • 此例中头结点和数据结点的类型不同

有序表

定义:线性表中的元素以递增或递减的顺序有序排列。

ADT OrderList
{
	数据对象:
		D = {ai | 1 <=i<= n}
	数据关系:
		R = {<ai, ai+1> | ai,ai+1 ∈ D && ai < ai+1}
	基本运算:
		InitList(&L):				初始化,构造新的线性表
		DestroyList(&L):			销毁线性表,释放L占用空间
		ListEmpty(L):				判断线性表是否为空	
		ListLength(L):				求线性表长度
		DispList(L):				输出线性表
		GetElem(L,i,&e):			求线性表中某个数据元素值
		LocateElem(L,e):			查找元素
		ListInsert(&L,e):		**仅插入与顺序表不同**	
		ListDelete(&L,i,&e):		删除元素
}

存储结构 & 基本运算

  1. 逻辑结构与线性表完全一致,可以采用 顺序表、链表 存储结构。
  2. 基本运算中只有 ListInsert() 算法与线性表有差异,其他都一样。
// 有序表的 顺序表 和 链表 插入

#define MaxSize 50
typedef struct OList
{
	int data[MaxSize];
	int length;
}SOList;

typedef struct LOList
{
	int data;
	struct LOList* next;
}LOList;

// 顺序表插入
void ListInsertS(SOList*& S, int e)
{
	int i = 0, j;
	while (i < S->length && S->data[i] < e)
		i++;
	for (j = S->length; j > i; j--)
	{
		S->data[j] = S->data[j - 1];
	}
	S->data[i] = e;
	S->length++;
}

// 链式插入
void ListInsertL(LOList*& L, int e)
{
	LOList* p,*pre = L;
	while (pre->next != NULL && pre->next->data < e)
		pre = pre->next;
	p = (LOList*)malloc(sizeof(LOList));
	p->data = e;
	p->next = pre->next;
	pre->next = p;	
}

归并算法

  • 将两个有序表在不改变两表 的前提下合并为一个有序表
// 顺序、链式 有序表的归并算法

#define MaxSize 20

typedef struct SqList
{
	int data[MaxSize];
	int length;
}SqList;

typedef struct LNode
{
	int data;
	struct LNode* next;
}LinkNode;

void UnionListS(SqList* S1, SqList* S2, SqList*& S)
{
	int i = 0, j = 0, k = 0;
	// 两表比较
	while (i<S1->length && j<S2->length)
	{
		if (S1->data[i] < S2->data[j])
		{
			S->data[k] = S1->data[i];
			i++; k++;
		}
		else
		{
			S->data[k] = S2->data[j];
			j++; k++;
		}		
	}
	
	// 如果一个表被比较完了,直接将另一个表的元素依次添加进去就可以
	while (i<S1->length)
	{
		S->data[k] = S1->data[i];
		i++; k++;
	}
	while (j<S2->length)
	{
		S->data[k] = S2->data[j];
		j++; k++;	
	}
	S->length = k;
}

void UnionListL(LinkNode* L1, LinkNode* L2, LinkNode*& L)
{
	LinkNode* pa = L1->next, * pb = L2->next, *r = L, * s;
	
	while (pa !=NULL && pb != NULL)
	{
		if (pa->data < pb->data)
		{
			s = (LinkNode*)malloc(sizeof(LinkNode));
			s->data = pa->data;
			r->next = s;
			r = s;
			pa = pa->next;
		}
		else
		{
			s = (LinkNode*)malloc(sizeof(LinkNode));
			s->data = pb->data;
			r->next = s;
			r = s;
			pb = pb->next;
		}
	}
	while (pa!=NULL)
	{
		s = (LinkNode*)malloc(sizeof(LinkNode));
		s->data = pa->data;
		r->next = s;
		r = s;
		pa = pa->next;
	}
	while (pb!=NULL)
	{
		s = (LinkNode*)malloc(sizeof(LinkNode));
		s->data = pb->data;
		r->next = s;
		r = s;
		pb = pb->next;
	}
	r->next = NULL;
}

思想是一样的,如果两个顺序表都还有元素,则需要进行比较,否则直接将还剩元素的表依次添加到新表即可

案例

  1. LA、LB、LC中找到三个有序表中都有的元素存放在LA中,释放LA中多余的元素
void Commoned(LinkNode*& LA, LinkNode* LB, LinkNode* LC)
{
	LinkNode* pa = LA->next, * pb = LB->next, * pc = LC->next, * q, * r;
	LA->next = NULL;
	r = LA;
	while (pa!=NULL)
	{
        // 找到pb pc 中大于或等于pa 的元素
		while (pb!=NULL && pb->data < pa->data)
			pb = pb->next;
		while (pc != NULL && pc->data < pa->data)
			pc = pc->next;
        // 判断到底大于还是等于
		if (pb->data != NULL && pc->data != NULL && pa->data == pb->data && pc->data == pa->data)
		{
            // 等于 ,插入到LA中
			r->next = pa;
			r = pa;
			pa = pa->next;
		}
		else
		{
            // 不等于,删除这个结点
			q = pa;
			pa = pa->next;
			free(q);
		}
	}
	r->next = NULL;
}
  1. 计算两个元素个数相同 顺序存储结构的升序 序列的中位数
// 中位数
int M_search(SOList* S1, SOList* S2)
{
	int i = 0, j = 0, k = 0;
	while (i<S1->length && j<S2->length)
	{
        // 只需要找到这个中位数的位置就可以了,中间元素不用管
		k++;
		if (S1->data[i] < S2->data[j])
		{
            // 判断当前元素是否为中位数位置
			if (k == S1->length)
				return S1->data[i];
			i++;
		}
		else
		{
			if (k == S2->length)
				return S2->data[j];
			j++;
		}
	}
}

ps. 萌新小白,欢迎私信或评论区交流指正,教材参考清华大学出版社《数据结构教程》第五版

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值