如果你有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 部分方法
-
a.begin(); a.end();//获得首节点,尾节点
-
a.push.back();a.pop_back();//从末端添加,删除节点
-
a.push_front();a.pop_back();//从前端添加,删除节点
-
a.empty();
-
a.clear();
-
a.front();a.back();//获得首元素,尾元素
-
a.reverse();
-
a.merge(b,greater<\int>());//合并a,b,结果放在a处,greater值升序,可改。实际上这是函数指针,所以你可以放入自定义函数。
-
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的从开始到结束的所有位置的元素。
-
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;
}