[C++/数据结构]1.2 线性表的链式存储

7 篇文章 0 订阅
4 篇文章 0 订阅

如果你有C++11的了解,我建议您直接跳掉 5 基于C++11新特性的改进,因为原本方法在造节点上必须一个个元素写出来,然后再手动连接,十分不便;此外这里的原本的方法是不可靠的,仅作参考

1 需要构造的函数

1. Status InitList L(LinkList &L);
//生成新节点作为表头,用头指针L指向头结点;把头结点的指针域置空

2.isEmpty
//判断是否为空即判断头结点指针域是否为空,因为当加入元素时,头结点的指针域是一定要指向第一个元素的地址

3.DestroyList
//引入一个新指针,他指向表头,然后直接释放这块地方;指向下一个,释放。。。重复重复;需要注意的是要保存地址指向下一个地方;

4.ClearList
//需要三个指针,不删除头结点,然后清除所有元素delete

5.ListLen
//弄个计数器

6.LocateElem
//查找,从头开始找

7. GetElem
//获取i位置的元素

8. InsertList
9. DeleteList
 //两者类似,增加(删减)插入位置元素的地址即可

2 节点的设计

关于节点的设计,我们当然可以像老师那样将链表名跟节点放在一起,但是在对象的设计角度来说,他们两个的“方法”将重叠,比如说一个链表的任意节点都可以使用初始化链表的操作。因此我觉得把他们设计成两个对象

由于这次的设计会涉及到两种对象,一个是链表本身(或者说头结点),另一个是普通节点,他们在一些操作上是不相容的,比如说我们想要计算这个链表有多长,但是我们不会计算一个节点有多长;我们会想在链表插入一个节点,但是我们不会对一个节点对象插入一个节点。

实际上这是只在单链表才需要考虑的,如果设置成双链表,或者循环列表,那么表头即链表名无所谓跟节点有区别。

typedef struct
{
	int age;
	float grade;
}ElemType;

class LNode
{
private:
	ElemType* elem;
	
public:
	LNode* next;
	LNode(ElemType*e,LNode* left=NULL)
	{
		elem = e;
		next = left;
	}
	void show()
	{
		cout << "age is: " << elem->age << " ,grade is: " << elem->grade << endl;
	}

};

lnode为左节点,一个节点里面存在两个数据,一个数据ElemType去存储复杂的数据类型,另一个LNode* next。

3 方法

以下方法我很多是偷懒没写全的,比如Insert的位置我没有判定是否合法,首节点和结尾的处理我也没做

class LinkList
{

public:
	LNode* next;

	LinkList()
	{
		next = NULL;
		cout << "Link List Inited!" << endl;
	}

	~LinkList()
	{

	}

	bool isEmpty()
	{
		if (next == NULL)
		{
			cout << "is Empty!" << endl;
			return 0;
		}
		else
		{
			cout << "Not Empty!" << endl;
			return 1;
		}
	}

	int ListLen()
	{
		auto p = next;
		int i = 0;
		while (p != NULL)
		{
			p = p->next;
			i++;
		}
		cout << "length is: " << i << endl;
		return i;
	}

	void Print()
	{
		auto p = next;
		while (p != NULL)
		{
			p->show();
			p = p->next;
		}
	}

//todo loc valid?
	void InsertList(LNode* n, int loc)
	{
		auto p = next;
		auto q = next;
		int i = 1, j = 1;
		//先找到loc位置上的元素地址,然后让n连接他
		while (i != (loc))
		{
			p = p->next;
			i++;
		}
		n->next = p;

		
		//后找到loc-1位置上的元素,然后让q连接n;
		//loc,loc-1查找的顺序不能调换
		while (j != (loc -1))
		{
			q = q->next;
			j++;
		}
		q->next = n;

	}

//todo loc valid?
	void DeleteList(int loc)
	{
		auto p = next;
		auto q = next;
		int i = 1, j = 1;
		while (i != (loc+1))
		{
			p = p->next;
			i++;
		}

		while (j != (loc - 1))
		{
			q = q->next;
			j++;
		}

		q->next = p;
	}

//todo loc valid?
	LNode* GetElem(int loc)
	{
		auto p = next;
		int i = 1;
		while (i<(loc))
		{
			p = p->next;
			i++;
		}
		return p;
	}
	

	int LocateElem(LNode* n, int (*cmp)(void* e1, void* e2))
	{
		auto p = next;
		int i = 1;
		for (p; p != NULL; p = p->next)
		{
			if (cmp(p, n) == 0)
			{
				cout << "Location Found:  " << i << endl;
				return i;
			}
			i++;
		}
		printf("No such elem!\n");
		return -1;
	}
	

};

测试文件

#include "Header.h"

int cmp_age(void* e1, void* e2)
{
	return (int)(((ElemType*)e1)->age - ((ElemType*)e2)->age);
}


int main()
{
	//创建链表
	LinkList L;
	//L.isEmpty();
	//节点数据以及创建节点
	ElemType p1 = { 1,1.1 };
	//ElemType q1 = { 14,11.11 };
	ElemType p2 = { 2,2.2 };
	ElemType p3 = { 3,3.3 };
	ElemType p4 = { 4,4.4 };
	LNode n1(&p1);
	LNode n2(&p2);
	LNode n3(&p3);
	LNode n4(&p4);

	//连接节点
	L.next = &n1;
	n1.next = &n2;
	n2.next = &n3;
	n3.next = &n4;

	L.isEmpty();
	L.Print();
	L.ListLen();

	//测试insert
	//ElemType q1 = { 14,11.11 };
	//LNode m1(&q1);
	//L.InsertList(&m1, 2);
	//L.ListLen();
	//L.Print();

	测试Delete
	//L.DeleteList(2);
	//L.ListLen();
	//L.Print();

	//测试Get
	//LNode m2(&p1);
	//m2 = *L.GetElem(2);
	//m2.show();


	测试Locate
	//ElemType q1 = { 14,11.11 };
	//LNode m1(&q1);
	//L.LocateElem(&m1, cmp_age);

	return 0;
}

4 STL中的list

list是一个双向链表容器,他不可以进行随机存取,只能顺序存储,但是可以进行有效的插入和删除。这跟我们的线性表链式存储是一致的。

4.1 初始化

  std::list<int> first;                                // empty list of ints
  std::list<int> second (4,100);                       // four ints with value 100
  std::list<int> third (second.begin(),second.end());  // iterating through second
  std::list<int> fourth (third);                       // a copy of third

  int myints[] = {16,2,77,29};
  std::list<int> fifth (myints, myints + sizeof(myints) / sizeof(int) );

4.2 部分方法

  1. a.begin(); a.end();//获得首节点,尾节点

  2. a.push.back();a.pop_back();//从末端添加,删除节点

  3. a.push_front();a.pop_back();//从前端添加,删除节点

  4. a.empty();

  5. a.clear();

  6. a.front();a.back();//获得首元素,尾元素

  7. a.reverse();

  8. a.merge(b,greater<\int>());//合并a,b,结果放在a处,greater值升序,可改。实际上这是函数指针,所以你可以放入自定义函数。

  9. a.insert(a.begin(),100); //在a的开始位置插入100。

    a.insert(a.begin(),2,200);// 在a的开始位置插入2个100。

    a.insert(a.begin(),b.begin(),b.end());//在a的开始位置插入b的从开始到结束的所有位置的元素。

  10. a.erase(a.begin()); 将l1的第一个元素删除。

    a.erase(a.begin(),a.end()); 将l1的从begin()到end()之间的元素删除。

4.3 应用

1 合并两个集合

#include <iostream>
#include <list>
#include <algorithm>
using namespace std;

template <class T>
list<T> combine(list<T> l1, list<T> l2)
{
    list<T> tmp(l1.begin(), l1.end());
    for (auto i : l2)
    {
        for (auto j : l1)
        {
            if (i == j)
            {
                break;
            }
            if (j == l1.back())
            {
                tmp.push_back(i);
            }
        }
    }
    return tmp;
}

int main()
{
    list<int> l1{1, 2, 3, 4};
    list<int> l2{5, 1};

    auto l3 = combine(l1, l2);
    for (auto i : l3)
    {
        cout << i << endl;
    }
    system("pause");
    return 0;
}

5 基于C++11新特性的改进

考虑到list<int> l1{1, 2, 3, 4};他有这样3个特性,1是可以直接进行任意数量数据的赋值,2是自动进行节点的连接,3是可以支持任意类型的数据。实际上这样的操作,1是需要initializer_list这样的技术,2则是内存管理分配的问题,3则是类模板问题。我这里仅做抛砖引玉的工作,实现1,2的特性,3的话您可以修改Node类来实现。

struct Node
{
    int val;
    Node *next;
    Node() : val(0), next(nullptr) {}
    Node(int x) : val(x), next(nullptr) {}
    Node(int x, Node *next) : val(x), next(next) {}
};

template <class T>
class Linklist
{
private:
    Node *List;
public:
//initializer_list<T>是一个以array为基础的容器,你可以放任意数量的元素进去
    Linklist(initializer_list<T> initList)
    {
		//创造一个链表(其实也就是头结点)
        List = new Node;
        //创造一个指针用来帮我进行遍历操作
        Node *head = List;
        for (auto i : initList)
        {
        //将表头的next指向一个新节点,节点里存放i
            head->next = new Node(i);
        //移动指针指向新的节点
            head = head->next;
        }
    }

    int len()
    {
        int len = 0;
        auto p = List->next;
        while (p != 0)
        {
            p = p->next;
            len++;
        }
        return len;
    }

    void print()
    {
        auto p = List->next;
        while (p != 0)
        {
            cout << p->val << endl;
            p = p->next;
        }
    }
};

int main()
{
    Linklist<int> L{1, 2, 3, 4};
    cout << L.len() << endl;
    L.print();
    system("pause");
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值