2021-12-19 数据结构——线性表(中) (c++代码和c代码)

1. 线性表的链式存储

不要求逻辑上相邻的两个元素物理上也相邻,通过"链"建立起数据之间的逻辑关系。线性表的链式存储结构的特点是==用一组任意的存储单元存储线性表的数据元素,这组存储单元可以是连续的,也可以是不连续的。==这就意味着,这些元素可以存在内存未被占用的任意位置。
在这里插入图片描述
链表的定义是递归的,它或者为空null,或者指向另一个节点node的引用,这个节点含有下一个节点或链表的引用,线性链表的最后一个结点指针为“空”(通常用NULL或“^”符号表示)。
在这里插入图片描述
插入、删除不需要移动数据元素,只需要修改"链"

template<class DataType>
struct Node
{
    DataType data;              //存储数据
    Node<DataType> *next;       //存储下一个结点的地址
};

结点由存放数据元素的数据域和存放后继结点地址的指针域组成。
在这里插入图片描述

2. 链式存储结构的抽象数据类型 (c里面的L应该是第一个空节点?第一个空节点即头结点序号是0!!)

2.1 模板

c++

template<class DataType>
class LinkList
{
public:
    LinkList();            //默认构造          
    LinkList(DataType a[], int n);   // 有参构造
    ~LinkList();                    //析构函数
    int Length();                   //参数
    DataType Get(int i);            //按位置查找
    int Locate(DataType x);         //按值查找
    void Insert(int i, DataType x); //插入
    DataType Delete(int i);         //删除
    void PrintList();               //打印输出
private:
    Node<DataType> *first;          //头指针
};

c语言

List L;

List MakeEmpty(); //初始化链表 
int Length(List L);  // 以遍历链表的方法求链表长度 
List FindKth(int K,List L);  // 按序号查找 
List Find(ElementType X,List L);  // 按值查找 
List Insert(ElementType X,int i,List L);  //将 X 插入到第 i-1(i>0) 个结点之后 
List Delete(int i,List L); // 删除第 i(i>0) 个结点 
void Print(List L); // 输出链表元素 

2.1.1 特点

用一组任意的存储单元存储线性表的数据元素, 这组存储单元可以存在内存中未被占用的任意位置
顺序存储结构每个数据元素只需要存储一个位置就可以了,而链式存储结构中,除了要存储数据信息外,还要存储它的后继元素的存储地址

2.2 初始化,构造

2.2.1 无参构造

生成只有空节点的空链表
c++

template<class DataType>
LinkList<DataType>::LinkList()
{
    first = new Node<DataType>;
    first->next = NULL;
}

c

// 初始化链表 
List MakeEmpty(){
	List L = (List)malloc(sizeof(struct LNode));
	L = NULL;
	return L;
}

2.3 头插法构造单链表

头插法是每次将新申请的结点插在头结点后面。
  在这里插入图片描述
c++

template<class DataType>
LinkList<DataType>::LinkList(DataType a[], int n)
{
    first = new Node<DataType>;
    first->next = NULL;
    for (int i = 0; i < n; i++)
    {
        Node<DataType> *s = new Node<DataType>;
        s->data = a[i];
        s->next = first->next; //这边这个first->next;指的是原来还没有插入的时候的节点的数据,这个时候正好就把要插入的s节点指向了s后面的节点的数据了。
        first->next = s;  
    }
}

c

/* 插入
1. 用 s 指向一个新的结点
2. 用 p 指向链表的第 i-1 个结点 
3. s->Next = p->Next,将 s 的下一个结点指向 p 的下一个结点 
4. p->Next = s,将 p 的下一结点改为 s   */
List Insert(ElementType X,int i,List L){
	List p,s;
	if(i == 1){     // 新结点插入在表头 ,如果插入的节点是头结点
		s = (List)malloc(sizeof(struct LNode));   //新插入的节点
		s->Data = X;  //x是数据域
		s->Next = L;
		return s;     //插入的结点为头结点 
	}
	p = FindKth(i-1,L);   // 找到第 i-1 个结点,
	if(!p){   // 第 i-1 个结点不存在   
		printf("结点错误");
		return NULL;
	}else{  //如果插入的不是头结点!!!!!!!!!也就是说随机插入的话
		s = (List)malloc(sizeof(struct LNode));
		s->Data = X;
		s->Next = p->Next;   //将 s 的下一个结点指向 p 的下一个结点   这边这个p->next;指的是原来还没有插入的时候的节点的数据,这个时候正好就把要插入的s节点指向了s后面的节点的数据了。
		p->Next = s;   // 将 p 的下一结点改为 s
		return L;
	}
}

2.4 尾插法构造单链表

尾插法就是每次将新申请的结点插在终端节点的后面
在这里插入图片描述

template<class DataType>
LinkList<DataType>::LinkList(DataType a[], int n)
{
    first = new Node<DataType>;
    Node<DataType> *r = first;
    for (int i = 0; i < n; i++)
    {
        Node<DataType> *s = new Node<DataType>;
        s->data = a[i];
        r->next = s;
        r = s;
    }
    r->next = NULL;
}

2.5 中间插入

单链表在插入过程中需要注意分析在表头、表中间、表尾的三种情况,由于单链表带头结点,这三种情况的操作语句一致,不用特殊处理,时间复杂度为 O(n)
在这里插入图片描述

template<class DataType>
void LinkList<DataType>::Insert(int i, DataType x)
{
    first = new Node<DataType>;
    Node<DataType> *p = first;  //???????????
    int count = 0;
    while (p != NULL && count<i - 1)  //count<i - 1!!!!!!!!
    {
        p = p->next;
        count++;
    }
    if (p == NULL) throw "Location";
    else {
        Node<DataType> *s = new Node<DataType>;
        s->data = x;
        s->next = p->next;  // p->next是未插入的时候指向的下一个数据
        p->next = s;
    }
}

总结插入元素:

还是c的代码比较牛,这样直接融会贯通了。

2.6 析构函数

单链表类中的结点是用new申请的,在释放的时候无法自动释放,所以,析构函数要将单链表中的结点空间释放

template<class DataType>
LinkList<DataType>::~LinkList()
{
    while (first != NULL)
    {
        Node<DataType>* q = first;
        first = first->next;
        delete q;
    }
}

C语言就需要析构了!。。

2.7 计算长度

单链表中不能直接求出长度,所以我们只能将单链表扫描一遍,所以时间复杂度为O(n)
  在这里插入图片描述
C++

template<class DataType>
int LinkList<DataType>::Length()
{
    Node<DataType>* p = first->next;
    int count = 0;
    while (p != NULL)
    {
        p = p->next;  //再次指向下一个
        count++;
    }
    return count;
}

C

//求表长 
int Length(List L){
	List p = L;
	int len=0;
	while(p){  // 当 p 不为空 
		p = p->Next;
		len++;
	}
	return len;
}

2.8 按位查找

单链表中即使知道节点位置也不能直接访问,需要从头指针开始逐个节点向下搜索,平均时间性能为O(n) ,单链表是顺序存取结构

template<class DataType>
DataType LinkList<DataType>::Get(int i)  // i代表的是 第几个 
{
    Node<DataType>* p = first->next;
    int count = 1;
    while (p != NULL && count<i)
    {
        p = p->next;
        count++;
    }
    if (p == NULL) throw "Location";
    else return p->data;
}
// 按序查找 
List FindKth(int K,List L){
	List p = L;
	int i = 1;  //从 1 开始 
	while(p && i<K){
		p = p->Next;
		i++;
	}
	if(i == K)    // 找到了 
		return p;
	else    // 未找到 
		return NULL;
}

2.9 按值查找

单链表中按值查找与顺序表中的实现方法类似,==对链表中的元素依次进行比较,平均时间性能为O(n) ==

template<class DataType>
int LinkList<DataType>::Locate(DataType x)
{
    Node<DataType> *p = first->next; //第一个空节点后面的带数据域的节点!!!!! 注意这个序号!
    int count = 1;
    while (p != NULL)
    {
        if (p->data == x) return count;
        p = p->next;
        count++;
    }
    return 0;
}
// 按值查找  
List Find(ElementType X,List L){
	List p = L;
	while(p && p->Data!=X)
		p = p->Next;
	// 找到了,返回 p
	// 未找到,返回 NULL,此时 p 等于 NULL 
	return p;   
} 

2.10 删除

删除操作时需要注意表尾的特殊情况,此时虽然被删结点不存在,但其前驱结点却存在。因此仅当被删结点的前驱结点存在且不是终端节点时,才能确定被删节点存在,时间复杂度为O(n) .
  在这里插入图片描述

template<class DataType>
DataType LinkList<DataType>::Delete(int i)  //注意::这个p是要删除的节点q的前一个节点
{
    Node<DataType> *p = first;
    int count = 0;
    while (p != NULL && count<i - 1)
    {
        p = p->next;
        count++;
    }
    if (p == NULL || p->next == NULL) throw "Location";
    else {
        Node<DataType> *q = p->next;
        int x = q->data;
        p->next = q->next;
        return x;
    }
}

c

/* 删除
1. 用 p 指向链表的第 i-1 个结点 
2. 用 s 指向要被删除的的第 i 个结点
3. p->Next = s->Next,p 指针指向 s 后面
4. free(s),释放空间 
*/
List Delete(int i,List L){
	List p,s;
	if(i==1){   //如果要删除头结点 
		s = L;
		if(L)   // 如果不为空 
			L = L->Next;
		else
			return NULL;
		free(s);   // 释放被删除结点 
		return L; 
	}
	p = FindKth(i-1,L);    // 查找第 i-1 个结点
	if(!p || !(p->Next)){     // 第 i-1 个或第 i 个结点不存在 
		printf("结点错误");
		return NULL;
	}else{
		s = p->Next;    // s 指向第 i 个结点 
		p->Next = s->Next;  //从链表删除 
		free(s);  // 释放被删除结点 
		return L;
	}
}

2.11 遍历

遍历单链表时间复杂度为O(n) .
  在这里插入图片描述

template<class DataType>
void LinkList<DataType>::PrintList()
{
    Node<DataType> *p = first->next;
    while (p != NULL)
    {
        cout << p->data << endl;
        p = p->next;
    }
}

c

// 输出链表元素 
void Print(List L){
	List t;
	int flag = 1;
	printf("当前链表为:");
	for(t = L;t;t =t->Next){
		printf("%d  ",t->Data);
		flag = 0;
	}
	if(flag)
		printf("NULL");
	printf("\n"); 
}

2.12 测试

c++

int main()
{
    LinkList<int> p;
    p.Insert(1, 6);
    p.Insert(2, 9);
    p.PrintList();
    p.Insert(2, 3);
    p.PrintList();
    cout << p.Get(2) << endl;
    cout << p.Locate(9) << endl;
    cout << p.Length() << endl;
    p.Delete(1);
    p.PrintList();
    return 0;
}

c

int main(){
	L = MakeEmpty();
	Print(L);
	L = Insert(11,1,L);
	L = Insert(25,1,L);
	L = Insert(33,2,L);
	L = Insert(77,3,L);
	Print(L);
	printf("当前链表长度为:%d\n",Length(L));
	printf("此时链表中第二个结点的值是:%d\n",FindKth(2,L)->Data);
	printf("查找22是否在该链表中:");
	if(Find(22,L))
		printf("是!\n");
	else
		printf("否!\n");
	printf("查找33是否在该链表中:");
	if(Find(33,L))
		printf("是!\n");
	else
		printf("否!\n");
	L = Delete(1,L);
	L = Delete(3,L);
	printf("----------删除后-----\n"); 
	Print(L);
	return 0;
} 

3. 链式存储的优缺点

优点:
插入、删除不需移动其他元素,只需改变指针.
链表各个节点在内存中空间不要求连续,空间利用率高
缺点:
查找需要遍历操作,比较麻烦

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值