引言
- 在顺序结构中,每个数据元素只需要存数据元素信息就可以了,链式结构中,除了要存数据元素信息外,还要存储它的后继元素的存储地址。
- 为了表示每个数据元素 ai 与其直接后继元素 ai+1 之间的逻辑关系,对数据元素 ai 来说,除了存储其本身的信息之外,还需存储一个指示其直接后继的信息(即直接后继的存储位置)。我们把存储数据元素信息的域称为数据域,把存储直接后继位置的域称为指针域。指针域中存储的信息称作指针或链。这两部分信息组成数据元素 ai 的存储映像,称为结点(Node)。
- n个结点(ai 的存储映像)链结成一个链表,即为线性表 ( a 1 , a 2 , ⋯   , a n ) (a_1,a_2,\cdots,a_n) (a1,a2,⋯,an)的链式存储结构,因为此链表的每个结点中只包含一个指针域,所以叫单链表。链表中第一个结点的存储位置叫做头指针。
struct Node
{
int data;
Node *next;
};
定义一个结构体,同一个结构可以存储多种类型的数据,比数组更灵活,可以将有关结点的信息放在一个结构中,从而将数据的表示合并到一起。
private:
Node *Head;
定义了一个单链表类,其中类的私有成员只有一个头指针,头指针具有标识作用,所以常用头指针冠以链表的名字,头指针是链表的必要元素。
LinkList::LinkList()
{
Head = new Node;
Head->next = NULL;
}
LinkList::~LinkList()
{
Node *p, *q;//q不可少,问题1
p = Head->next;
while (p)//p最后指向一个空结点
{
q = p->next;
delete p;
p = q;
}
Head->next = NULL;
}
构造函数的定义是创建一个空链表。析构函数是去遍历这个链表,然后将该链表重新变成一个空链表。在析构函数中,要实现定义一个新结点,不需要为其分配内存,它起到临时存放结点的作用。
void LinkList::CreaetListHead(int n)
{
srand(time(0));
//Node *p=new Node;//定义错误,问题2
Node *p;
for (int i = 0; i < n; i++)
{
p = new Node;
p->data = rand() % 100 + 1;//随机产生1-100
p->next = Head->next;
Head->next = p;
}
}
头插法整表创建一个单链表,结点中的数据成员随机产生。在定义该函数时,要注意结点的内存空间要在循环内部分配,也就是每创建一个结点就要重新给新结点分配一段内存空间。
void LinkList::CreaetListTail(int n)
{
srand(time(0));
Node *p;
Node *r;
r = Head;
for (int i = 0; i < n; i++)
{
p = new Node;
p->data = rand() % 100 + 1;
r->next = p;
r = p;//r重新变回尾结点
}
r->next = NULL;
}
尾插法整表创建一个单链表,结点中的数据成员随机产生。与头插法不同的是,每次的尾结点会发生改变,每创建一个新结点就要将该结点变成新的尾结点,然后指向新的尾结点,最后插入的结点结构成员next要赋为空。
void LinkList::CreateListHeadV(int value)
{
Node *p = new Node;
p->data = value;
p->next = Head->next;
Head->next = p;
}
void LinkList::CreateListHeadP(Node *p)
{
p->next = Head->next;
Head->next = p;
}
头插法分别插入一个值和一个新结点,插入值时需要定义一个新结点并为其分配一段内存空间,然后将要插入的值赋给数据成员,然后执行与插入结点相同的操作即可。因为若插入一个结点,该结点事先已经被初始化了。
void LinkList::CreateListTailV(int value)
{
Node *p = new Node;
p->data = value;
Node *r = Head;
while (r->next)//找出最后一个不为空的结点,问题3
{
r = r->next;
}
r->next = p;
p->next = NULL;
}
void LinkList::CreateListTailP(Node *p)
{
Node *r = Head;
while (r->next)
{
r = r->next;
}
r->next = p;
p->next = NULL;
}
尾插法分别插入一个值和一个新结点,与头插法不同的是,我们要遍历寻找最后一个不为空的结点,然后才能进行相应的操作。
void LinkList::InsertListBe(Node *p, int value)
{
Node *q = new Node;
q->data = value;
Node *r = Head->next;
while (r)
{
if (r->data == p->data && r->next == p->next)
{
q->next = r->next;
r->next = q;
return;
}
else
r = r->next;
}
}
后插法,该函数执行在某个确定的结点后执行插值操作,插值操作与前面尾插法类似,但此时我们寻找的不是最后一个不为空的结点,而且给定的结点。
void LinkList::InsertListFr(Node *p, int value)
{
Node *q = new Node;
q->data = value;
Node *r = Head->next;
Node *qH = Head;
while (r)
{
if (r->data == p->data && r->next == p->next)
{
q->next = r;
qH->next = q;
}
qH = r;
r = r->next;
}
}
前插法,该函数执行在某个确定的结点前执行插值操作,插值操作与后插法类似,但在遍历找寻该结点的时候,*qH 和 *r 的位置会不断发生改变。
- 插入法关键操作为q->next=p->next;p->next=q;
void LinkList::DeleteList(Node *p)
{
Node *r = Head;
while (r->next)
{
if (r->next == p)
{
r->next = p->next;
delete p;
}
else
r = r->next;
}
}
该函数执行删除某个给定结点的操作,先找到该结点。
- 删除法关键操作为p=r->next;r->next = p->next;
void LinkList::SearchList(int value)
{
Node *r = Head;
while (r->next)
{
if (r->next->data == value)
{
cout << "find" << value << endl;
r = r->next;
}
else
r = r->next;
}
}
该函数执行查找某值操作,遍历单链表,若结点的成员data与给定值相同,则输出找到操作,然后继续遍历下一个结点,直到查找到空结点。
void LinkList::printList()
{
Node *r = Head->next;
while (r)
{
cout << r->data << '\t';
r = r->next;
}
cout << endl;
}
该函数实现链表数据的输出操作。
注意问题
1、在主函数中定义一个类的一个对象时,要对其进行初始化操作。
2、执行结点的插入操作时,该结点已经发生了改变,下次就不能再次对该结点进行插入操作,但可以进行查找或删除相关操作。
- 本人在后面实现了一段主函数的操作,仅作参考。
int main()
{
LinkList L;
Node *p = new Node;
p->data = 5;
Node *q = new Node;
q->data = 3;
Node *r = new Node;
r->data = 3;
L.CreaetListHead(3);
L.printList();
L.CreaetListTail(3);
L.printList();
L.CreateListHeadV(5);
L.printList();
L.CreateListTailV(9);
L.printList();
L.CreateListHeadP(p);
L.printList();
//再次调用时,p后面都为空了,整个链表就一个数据,问题4
L.CreateListTailP(q);
L.printList();
L.CreateListTailP(r);
L.printList();
L.DeleteList(p);
L.printList();
L.SearchList(3);
L.InsertListBe(r, 8);
L.printList();
L.InsertListFr(r, 9);
L.printList();
return 0;
}