一、实现基础
- 代码基于C++的模板类实现,这样就能够自由选择链表的数据类型。
- 使用指针实现有头结点的单链表。
- 在链表类Node中只定义了数据结构和功能函数,head头结点在主函数中定义并作为函数参数,这一点较常用的链表实现有所区别。
二、实现功能
- 创建链表
- 返回链表长度
- 在链表尾部添加元素
- 插入元素
- 删除结点并返回该结点的值
- 清空链表
- 按链表值从尾到头的顺序打印链表(附加功能)
三、各功能详解
1、链表类 class Node
template <class T>
class Node
{
public:
T val;
Node *next;
Node(T x):val(x), next(NULL) {} // 构造函数
void SetList(Node** head ); // 创建链表
int length(Node* head); // 返回链表长度
void append(T element, Node* head); // 在链表尾部添加元素
void insert(int pos, T element, Node* head); // 插入元素
T remove(int pos, Node* head, T errorFlag); // 删除结点并返回该结点的值
void clear(Node** head); // 清空链表
vector<int> printListFromTailToHead(Node* head); // 按链表值从尾到头的顺序打印
};
- 链表的结点值
val
为模板T
类型的,这样自己在创建链表时,就可以指定结点值为int/char
等各种类型,比较灵活。
2、创建链表
template <class T>
void Node<T>::SetList(Node** head){
Node <int> *cur, *temp;
*head = cur = temp = NULL;
for(int i = 0; i < Length; i++)
{
int t;
cin >> t;
temp = new Node<int>(0);
temp->val = t;
temp->next = NULL;
if(*head == NULL)
*head = temp;
else
cur->next = temp;
cur = temp;
}
}
-
为什么传参是
Node**
类型?head在主函数中的定义是
Node *head
,如果传参为*head
,会出现在SetList()
中head
指向的地址有变,但是在主函数中head
指向的地址没有变的情况。由于在主函数中初始化时head = NULL
,所以,如果传参为*head
,那么在SetList()
中确实head
指向了下一个结点的地址,但是主函数中head
指向的地址仍然为空,也就是没有创建链表。这是因为,函数参数传递的只能是数值,所以当指针作为函数参数传递时,传递的是指针的值,而不是地址,所以在函数中不能改变实际的指针指向的地址,解决该问题,将指针指向的地址作为参数传给函数即可,也就是传参为
Node**
。
3、返回链表长度
template <class T>
int Node<T>::length(Node* head){
Node<int> *cur = head;
int len = 0;
if(head == NULL)
return -1;
while(cur){
len++;
cur = cur->next;
}
return len;
}
4、在链表尾部添加元素
template <class T>
void Node<T>::append(T element, Node* head){
Node *temp, *cur = head;
temp = new Node<T>(0);
temp->val = element;
temp->next = NULL;
while(cur->next)
cur = cur->next;
cur->next = temp;
}
- 当找到链表的最后一个元素
tail
时(tail->next == NULL
),将原本tail
指向为空替换为指向新增加的结点temp
,再将temp->next
赋值为NULL
,即temp
成为了新的tail
。
5、插入元素
template <class T>
void Node<T>::insert(int pos, T element, Node* head){
Node *temp, *cur;
cur = new Node<T>(0);
temp = new Node<T>(0);
if((pos >= head->length(head)) || (pos <= 0)){
cout << "Invalid operate!" << endl;
return ;
}
else{
for(int i = 0; i < pos; i++){
cur = head;
head = head->next;
}
temp->val = element;
cur->next = temp;
temp->next = head;
}
}
- 插入元素和在链表尾部添加元素的原理是一样的,只不过多了
temp->next
指向下一个结点的步骤。 - 要注意判断插入元素的位置是否为合法位置。
- 这里没有讨论插入到最开始的情况,原理其实一样的,只不过要改变
head
指向的地址。对于一般的线性表,head
是作为类成员定义的,实现起来与插入操作大同小异,而本代码要实现插入到最开始的情况,将head
以Node** head
的形式传参即可。
6、删除结点并返回该结点的值
/* 返回被删除结点的val值 */
template <class T>
T Node<T>::remove(int pos, Node* head, T errorFlag){
Node<T> *temp, *cur = head;
temp = new Node<T>(0);
if((pos <= 0) || (pos >= head->length(head))){
cout << "pos error!" << endl;
return errorFlag;
}
for(int i = 1; i < pos; i++){
cur = cur->next;
}
temp = cur->next;
cur->next = cur->next->next;
return temp->val;
}
- 要注意判断删除的位置是否合法。
- 函数参数中有一个
T errorFlag
,这是因为函数的返回值类型为T
类型的结点值,所以当位置不合法时,也必须返回一个T
类型的值。比如T为int时,errorFlag = -1;T为char时,errorFlag = 'F'
。
7、清空链表
template <class T>
void Node<T>::clear(Node** head){
Node *temp;
temp = *head;
while(*head){
temp = *head;
*head = (*head)->next;
delete temp;
temp = NULL;
}
delete *head;
*head = NULL;
}
- 这里之所以传参为
Node** head
与前面一样。 - 有一个比较细微的知识点:如果只是
delete
指针,而不把指针指向NULL
,那么该指针会成为野指针,指向内存中的任意空间,所以最好在delete
之后把指针指向NULL
。
8、按链表值从尾到头的顺序打印链表
template <class T>
vector<int> Node<T>::printListFromTailToHead(Node* head) {
vector<int> ArrayList;
Node *temp = head;
ArrayList.clear();
while(temp != NULL){
ArrayList.insert(ArrayList.begin(),temp->val);
temp = temp->next;
}
return ArrayList;
}
- 原理很简单,用到了
vector
。
四、完整代码
#include <iostream>
#include <cstdio>
#include <vector>
using namespace std;
#define Length 10 /* 链表长度 */
template <class T>
class Node
{
public:
T val;
Node *next;
Node(T x):val(x), next(NULL) {} // 构造函数
void SetList(Node** head ); // 创建链表
int length(Node* head); // 返回链表长度
void append(T element, Node* head); // 在链表尾部添加元素
void insert(int pos, T element, Node* head); // 插入元素
T remove(int pos, Node* head, T errorFlag); // 删除元素
void clear(Node** head); // 清空链表
vector<int> printListFromTailToHead(Node* head); // 按链表值从尾到头的顺序打印
};
template <class T>
void Node<T>::SetList(Node** head){
Node <int> *cur, *temp;
*head = cur = temp = NULL;
for(int i = 0; i < Length; i++)
{
int t;
cin >> t;
temp = new Node<int>(0);
temp->val = t;
temp->next = NULL;
if(*head == NULL)
*head = temp;
else
cur->next = temp;
cur = temp;
}
}
template <class T>
int Node<T>::length(Node* head){
Node<int> *cur = head;
int len = 0;
if(head == NULL)
return -1;
while(cur){
len++;
cur = cur->next;
}
return len;
}
template <class T>
void Node<T>::append(T element, Node* head){
Node *temp, *cur = head;
temp = new Node<T>(0);
temp->val = element;
temp->next = NULL;
while(cur->next)
cur = cur->next;
cur->next = temp;
}
template <class T>
void Node<T>::insert(int pos, T element, Node* head){
Node *temp, *cur;
cur = new Node<T>(0);
temp = new Node<T>(0);
if((pos >= head->length(head)) || (pos <= 0)){
cout << "Invalid operate!" << endl;
return ;
}
else{
for(int i = 0; i < pos; i++){
cur = head;
head = head->next;
}
temp->val = element;
cur->next = temp;
temp->next = head;
}
}
/* 返回被删除结点的val值 */
template <class T>
T Node<T>::remove(int pos, Node* head, T errorFlag){
Node<T> *temp, *cur = head;
temp = new Node<T>(0);
if((pos <= 0) || (pos >= head->length(head))){
cout << "pos error!" << endl;
return errorFlag;
}
for(int i = 1; i < pos; i++){
cur = cur->next;
}
temp = cur->next;
cur->next = cur->next->next;
return temp->val;
}
template <class T>
void Node<T>::clear(Node** head){
Node *temp;
temp = *head;
while(*head){
temp = *head;
*head = (*head)->next;
delete temp;
temp = NULL;
}
delete *head;
*head = NULL;
}
template <class T>
vector<int> Node<T>::printListFromTailToHead(Node* head) {
vector<int> ArrayList;
Node *temp = head;
ArrayList.clear();
while(temp != NULL){
ArrayList.insert(ArrayList.begin(),temp->val);
temp = temp->next;
}
return ArrayList;
}
int main(int argc, char *argv[])
{
Node <int> *head, *cur, *temp;
head = cur = temp = NULL;
/* 创建链表 */
head->SetList(&head);
/* DEBUG START 测试链表是否创建成功 */
printf("\nSet a list:\n");
temp = head;
while(temp != NULL)
{
cout << temp->val << " ";
temp = temp->next;
}
printf("\nlength of the list is : %d\n",head->length(head));
/* DEBUG END */
/* 在链表尾部插入元素 */
printf("\nAppend the element 11 in the tail of the list:\n");
head->append(11,head);
/* DEBUG START */
temp = head;
while(temp != NULL)
{
cout << temp->val << " ";
temp = temp->next;
}
printf("\nlength of the list is : %d\n",head->length(head));
/* DEBUG END */
/* 插入元素到不合法位置--大于链表长度 */
printf("\nInsert the element 21 in the position out of the list(position 11):\n");
head->insert(head->length(head),21,head);
/* DEBUG START */
temp = head;
while(temp != NULL)
{
cout << temp->val << " ";
temp = temp->next;
}
printf("\nlength of the list is : %d\n",head->length(head));
/* DEBUG END */
/* 插入元素到合法位置 */
printf("\nInsert the element 21 in a right position(position 7):\n");
head->insert(7,21,head);
/* DEBUG START */
temp = head;
while(temp != NULL)
{
cout << temp->val << " ";
temp = temp->next;
}
printf("\nlength of the list is : %d\n",head->length(head));
/* DEBUG END */
/* 插入元素到不合法位置--链表头部 */
printf("\nInsert the element 21 in position 0: \n");
head->insert(0,21,head);
/* DEBUG START */
temp = head;
while(temp != NULL)
{
cout << temp->val << " ";
temp = temp->next;
}
printf("\nlength of the list is : %d\n",head->length(head));
/* DEBUG END */
/* 删除元素 */
printf("\ndelete the element in position length/2: \n");
int delete_value = head->remove((head->length(head))/2,head,-1);
/* DEBUG START */
printf("The value of the delete Node is : %d\n",delete_value);
temp = head;
while(temp != NULL)
{
cout << temp->val << " ";
temp = temp->next;
}
printf("\nlength of the list is : %d\n",head->length(head));
/* DEBUG END */
/* 按链表值从尾到头的顺序打印 */
vector<int> vec;
printf("\nprint List From Tail To Head: \n");
vec = head->printListFromTailToHead(head);
/* DEBUG START */
for (int i = 0; i < vec.size(); i++) {
cout << vec[i] << " ";
}
/* DEBUG END */
/* 清空链表 */
printf("\n\nClear the list:\n");
head->clear(&head);
cout << "head = " << head <<endl;
printf("length of the list is : %d\n",head->length(head));
return 0;
}
/*
样例
1 2 3 4 5 6 7 8 9 10
*/
- 在主函数中写了各个函数的测试代码,代码最后面给出了测试样例,编译运行程序,直接输入样例就可以查看结果。
- 前面提到的野指针可以通过删除
clear()
函数中的指针指向NULL
,然后输出指针地址,来查看实际现象。 - 同样的指针的传参是值传参也可以通过将
SetList()
函数或者clear()
函数中的Node** head
参数改为Node* head
并在定义函数和主函数中分别输出指针地址来查看实际现象。