数据结构-2.线性表


2.1

抽象运算(算法2.1)

例2-1 利用两个线性表LA和LB分别表示集合A和B,求一个新的集合A=A∪B。

算法思路:集合中的元素间是松散的关系,只需将在线性表LB中而不在LA中的数据元素追加到LA的尾部即可。

void union(List &La, List Lb) {
	// 获取线性表的长度
	La_len = Length(La);
	Lb_len = Length(Lb);
	
	// 遍历Lb中的每个元素
	for(i = 1;i <= Lb_len; i++) {
		// 获取Lb线性表中的第j个元素,存储到e中
		GetElem(Lb, i, e);
		// 判断在La线性表中根据 equal比较 是否存在e 
		if  (!LocatElem(La, e, equal))
			// 不存在就尾插插入e到La中
			InsertElem(La, ++La_len,e);
		}
}

2.2

顺序表存储的C语言实现

  • 静态分配
#define	MaxSize 100		// 线性表的最大长度
typedef struct {
	ElemType data[MaxSize];	
	int length;			// 当前长度
}SqList;
  • 动态分配
#define	InitSize 100	//空间初始分配量
typedef struct {
	ElemType *data;		// 指示动态分配数组的指针
	int length;		// 当前长度
	int MaxSize;	// 数组的最大容量
}SqList;
// C
L.data = (ElemTpye*)malloc(sizeof(ElemType)*InitSize);
// C++
L.data = new ElemType[InitSize];

创建一个空的顺序表(算法2.3)

// 引用
Status InitList(SqList &L) {
	// 分配
	L.data = (ElemTpye*)malloc(sizeof(ElemType)*InitSize);
	// 分配失败
	if (!L.data) exit(OVERFLOW);
	// 线性表的当前长度0
	L.length = 0;
	// 线性表的存储容量
	L.MaxSize = MAX_SIZE;
	return  OK;
}

用类C语言实现顺序表的插入

// 王道
// 数组从0开始
// 插入到第i个位置[1, n]
bool InsertList(SqList &L, int i,ElemType e)
{
	// 合法:插入到的第i个[1, n+1],n+1表示表尾增加
	if(i < 1 || i > L.length + 1)
	{
		return false;
	}
	// 满了
	if(L.length >= MaxSize)
	{	
		return false;
	}
	// 后移,倒着,对应数组下标[i-1,n-1]→[i,n]
	for(int j = L.length; j >= i; j--)
	{
		L.data[j] = L.data[j-1];
	}
	// 第i个对应数组下标i-1
	L.data[i-1] = e;
	L.length++;
	return true;
}
// 野生PPT
// 这里是C语言的数组定义(从0开始),而不是伪代码的数组定义(从1开始)
// 将元素x插入在线性表L的第i个元素位置上,即对应下标i-1。注意,是第几个位置,不是直接插入下标
Status ListInsert_sq(SqList &L, int i, ElemType x)
{
    // 越界
    if (i < 1 || i > L.length + 1) return ERROR;
    // 没用存储空间,要新增
    if (L.length >= L.Listsize)
    {
    	// 重新分配 realloc,大小是 原来的listsize + LISTINCREMENT 增量
        newbase = (ElemType *)realloc(L.elem, (L.listsize + LISTINCREMENT) * sizeof(ElemType));
        // 分配失败
        if (!newbase) exit(OVERFLOW);
        // 线性表的存储空间基址更新
        L.elem = newbase;
        // 线性表的容量更新
        L.listsize += INCREMENT;
    }
    // q为插入位置, C语言数组下标从0开始
    q = &(L.elem[i - 1]); 
    // 移动元素并插入,倒着从最后一个开始,到包括要插入的那个位置
    for (p = &(L.elem[L.length - 1]); p >= q; --p)
    {
    	// 后一个元素赋值前一个,表示后移元素
        *(p + 1) = *p;
	}
	// 空出来的位置就是要插入的元素
    *q = x;
    // 线性表的当前长度更新
    L.length++;
    return OK;
} //ListInsert_sq

删除

// 王道
// 数组从0开始
// 删除到第i个位置[1, n]
bool ListDelete(SqList &L, int i, ElemType &e)
{
	// 合法:删除的第i个[1, n]
	if(i < 1 || i > L.length)
	{
		return false;
	}
	// 对应
	e = L.data[i-1];
	// 前移:对应数组[i, n-1]→[i-1, n-2]
	for(int j=i; j<L.length;j++)
	{	
		L.data[j-1] = L.data[j];
	}
	L.length--;
	return true;
}

按值查找(顺序查找)

// 王道
// 数组从0开始
// 成功返回第i个位置[1, n],失败返回0
int LocateELem(SqList L, ElemType e)
{	
	for(int i = 0; i < L.length; i++)
	{
		// 数组下标i对应第i+1个
		if(L.data[i] == e)	return i+1;
	}
	return 0;
}

2.3

2.3.1.单链表

单链表的C语言实现

typedef struct LNode{
	ElemType data;		//数据域
	struct LNode *next;	//指针域
} LNode, *LinkList;
// LinkList表示结点的指针

用类C语言实现单链表上的查询(查找第i个元素):

// L是带头结点的单链表的头指针,注意头结点的特殊含义(第一个结点没有数据,第二个结点才是第一个元素)。
// i表示第i个元素,而不是下标
// 效果:当第i个元素存在时,将该元素的值赋给e并返回OK,否则返回ERROR
Status GetElem_L(LinkList L, int i, ElemType &e)
{
    // 初始化,p指向第一个元素,k为计数器
    p = L->next;
    // k=1,从第一个元素开始,保证了i<1的非法情况直接排除
    k = 1; 
    // 顺指针向后查找:p不空表示合法结点,k<i表示还要继续遍历
    while (p && k < i)
    { 
        p = p->next;
        ++k;
    } // 循环的退出条件为 到达单链表结尾(P为空) 或 k=i(没找到,或找到了)
	// 第i个元素不存在(空,遍历完没找到)
    if (!p || k > i) return ERROR;
    // 赋值
    e = p->data;
    return OK;
} //GetElem_L

查找第[1,n]个元素处,p对应第[1,n]个元素

思考: “本算法为何没有考虑 i值的合法性?”
两种非法:i<1和 i>n

  • i<1(i<=0):因为while的循环条件k < i,此时k>i,非法情况直接排除。
  • i>n:p空了。

单链表上的插入运算(第i个元素位置上插入新的结点)

// 在带头结点的单链表L中第i个元素位置之前插入元素e
// 第i个
Status ListInsert_L(LinkList &L, int i, ElemType e)
{
	// 初始化,p指向头结点,k为计数器
    p = L;
    // k=0,表示找到的是i前的那个元素,还保证了i<1的非法情况直接排除
    k = 0; 
    // 遍历,p不空,k<i-1(++k出来就是k=i-1)
    while (p && k < i - 1)
    { 
        p = p->next;
        ++k;
    } // 逐步移动指针p,直到 p为空 或 p指向第i-1个元素
    // 第i个元素不存在(p空,遍历完没找到)
    if (!p || k > i - 1) return ERROR;
    // 申请元素e的结点空间,并判断失败否
    if (!(s = (LinkLinst)malloc(sizeof(LNode)))) 
        return OVERFLOW;
    // 对应②赋值e
    s->data = e;
    // 对应③
    s->next = p->next;
    // 对应④
    p->next = s;
    return OK;
} //LinstInsert_L

合法区间:插入到第[1,n+1]个元素处,p对应第[0,n]个元素。

  • i<1:失败。
  • i>n+1:失败。

单链表上的删除运算(把第i个元素删除)

// 在带头结点的单链表L中,删除第i个元素,并由e返回其值
// 第i个
Status ListDelete_L(LinkList &L, int i, ElemType &e)
{
	// 初始化,p指向头结点,k为计数器
    p = L;
    // k=0,表示找到的是i前的那个元素,还保证了i<1的非法情况直接排除
    k = 0; 
    // 遍历,p的下一个结点不空,k<i-1(++k出来就是k=i-1)
    while (p->next && k < i - 1)
    {
        p = p->next;
        ++k;
    }
    // 第i个元素不存在(p->next空,遍历完没找到)
    if (!p->next || k > i - 1) return ERROR;
    // 对应②:增设临时指针q指向p的下一结点(待删除的结点);
    q = p->next;       
    // 对应③:修改结点p的指针域,指向第i-1个结点(p->next=p->next ->next);
    p->next = q->next;
    // 由e返回其(要删除的结点)值
    e = q->data;
    // 对应④:删除结点q,释放内存空间。
    free(q);
    return OK;
} //ListDelete_L

思考: while语句中的p->next 可否用p替代?
不行,因为要保证p的下一个结点不能空。

合法区间:第[1,n]个元素,p对应第[0,n-1]个元素

用类C语言实现表头插入算法:

// 用表头插入法逆序建立带头结点的单链表
void CreateList_L(LinkLinst &L, int n)
{
	// 未做溢出判定,应加入
    L = (LinkList)malloc(sizeof(LNode)); 
    // 带头结点的空链表
    L->next = NULL;
    // 建立头结点,假定与元素结点结构相同
    // 因为是逆序,所以从后向前输入元素
    for (i = n; i > 0; --i)
    { 
	    // 生成新结点
        p = (LinkList)malloc(sizeof(LNode));
        // 从键盘输入元素值,存入新结点中
        scanf(&p->data); 
        // 新结点的下一个结点是头结点的下一个结点
        p->next = L->next;
        // 头结点的下一个结点是新节点
        L->next = p; 
    }
}

用C语言实现表头插入算法:

#include <stdio.h>
#include <stdlib.h>

typedef int ElemType;
typedef struct LNode
{
    ElemType data;      // 数据域
    struct LNode *next; // 指针域
} LNode, *LinkList;

void ListDelete_L(LinkList &L, int n)
{
    // 未做溢出判定,应加入
    L = (LinkList)malloc(sizeof(LNode)); 
    // 空链表
    L->next = NULL;

    // p为指向结点的指针类型
    LinkList p = NULL;
    for (int i = n; i > 0; --i)
    {                                       //从后向前输入元素
        p = (LinkList)malloc(sizeof(LNode)); //未做溢出判定,应加
        scanf("% d", &p->data);//将键盘输入置入p的数据域
        p->next = L->next;
        L->next = p;
    }
}

用类C语言实现两个有序单链表的合并

// 归并两个非递减带头结点的单链表La、Lb到Lc中(相同项都保留),且使Lc非递减
void MergeList_L(LinkList &La, LinkList &Lb, LinkList &Lc)
{
    // 用于遍历的指针指向第一个元素
    pa = La->next;
    pb = Lb->next;
    // 带头结点的空链表
    Lc = (LinkList)malloc(sizeof(LNode)); 
    Lc->next = NULL;
    // 用于遍历的指针
    pc = Lc;
    // 都不为空
    while (pa && pb)
    {
        if (pa->data <= pb->data)
        {
            // pc的下一个结点接pa
            pc->next = pa;
            // pc更新为最后一个
            pc = pa;
            // pa更新为下一个
            pa = pa->next;
        }
        else
        {
            // pc的下一个结点接pb
            pc->next = pb;
            // pc更新为最后一个
            pc = pb;
            // pb更新为下一个
            pb = pb->next;
        }
    }
    // 至少一个链表走完了,那就不用比较了,直接接另一个链表的剩下部分
    pc->next = pa ? pa : pb;
} // MergeList_L

2.3.2.循环单链表和双向链表

双向链表的C语言描述

typedef struct DuLNode
{
    ElemType data;
    struct DuLNode *prior;
    struct DuLNode *next;
} DuLNode, *DulinkList;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值