链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。
- 相比于顺序表,其具有如下优点:
- 线性链表无需实现定义链表最大长度,只要存储空间充足,就可以增加链表结点,不存在溢出问题。
- 线性链表插入和删除操作简单,只需要修改链接指针,无需大量元素移动,时间复杂度为O(1)。
现在准备找工作了,于是打算复习一下以前学过的,大学三年里一直都没怎么认真过,现在我有点慌了,于是打算写篇博客来记录一下,算是补补旧账。当然如果能给有需要的人帮助那就最好了。就个人感觉,顺序表和线性链表是数据结构中最简单的两个了,但很重要。
链表结点类、链表类的声明:
class listNode//链表结点
{
public:
int element;
listNode *next;
//构造函数
listNode() {}
listNode(int ele, listNode *l)
{
element = ele;
next = l;
}
};
class listArray//链表类
{
listNode* head;//头结点指针
public:
//构造、析构函数
listArray() {}
listArray(listNode *h)
{
head = h;
}
~listArray()
{
delete head;
}
void creat(int num);//创建num个结点的链表
void insert(listNode* node,int pos);//把一个结点插入到pos位置
void del();//删除所有节点
void trans();//转置
void dis();//正序打印
void delByItem(int val);//按值删除结点
void disDes(listNode* h);//倒序打印
int length();//链表长度
listNode* getHead();//返回链表头结点
};
链表的创建:
首先new一个链表的头结点,给其赋初值,再把head指向头结点;这里声明了一个指向新生成结点前一个的指针temp,在生成后续结点循环内,每new一个结点对象,就把temp->next指向新生成的结点对象,在把temp指向新生成的结点对象。
void listArray::creat(int num)
{
if (num > 0)
{
listNode* firstNode = new listNode(1,NULL);
listNode* temp = firstNode;
head = firstNode;
for (int i = 1;i < num;i++)
{
listNode* newNode = new listNode(i+1,NULL);
temp->next = newNode;
temp = newNode;
}
}
}
链表的插入:
链表插入的位置有三种:头结点之前,链表结点中、链表末尾。我这里pos=0表示头结点前,pos=n(n<=链表长度)表示第n个结点后插入。
void listArray::insert(listNode* node,int pos)
{
int length = this->length();
if (pos == 0)
{
node->next = head;
this->head = node;
}
else if (pos > 0 && pos <= length)
{
listNode* t = head;
for (int i = 1;i < pos;i++)
{
t = t->next;
}
node->next = t->next;
t->next = node;
}
else
{
cout << "插入位置无效。" << endl;
}
}
链表的打印:
在while循环内打印链表。首先用一个结点指针t来遍历所有结点,只要t不为NULL就打印结点。
void listArray::dis()
{
listNode* t = head;
if (head !=NULL)
{
while (t != NULL)
{
if (t->next != NULL)
cout << t->element << "->";
else
cout << t->element << endl;
t = t->next;
}
}
else
cout << "空链表。" << endl;
}
链表的转置:
链表转置需要记录三个位置结点的指针,由于head也需要变动,所以只用额外声明两个结点指针p、q(好久没写代码了,当时想了好久才明白T-T)。
void listArray::trans()
{
listNode* p = NULL, *q = head;
while (head!=NULL)
{
q = q->next;
head->next = p;
p = head;
head = q;
}
head = p;
}
链表的倒序输出:
相信很多人看到这个第一时间想到的就是用栈了吧。。毕竟栈的最大特性就是先进后出。不过在百度一番后还有很多实现方法,这里我选了递归(讲道理学了这么久,这递归还真是没学明白个所以然。递归最经典的问题就是阶乘了,想必大家都很熟悉了。我对递归理解是:问题的求解需要下一个问题的答案,这让不断调用自身,直至递归到边界,然后一层层返回求出结果。我感觉我的理解没问题,实可际就是不会用,这就很尴尬了。递归最大的好处就是代码简洁明了,还需要补补啊!)
void listArray::disDes(listNode* h)
{
if (h != NULL)
{
if (h->next != NULL)
{
disDes(h->next);
}
if (h != head)
cout << h->element << "->";
else
cout << h->element;
}
}
链表删除指定值结点:
最直接的想法就是遍历所有结点,找出要删除的结点,记录它的前一个结点,然后就把前一个结点的next指针指向所要删除结点的后一个结点。然后在寻找下一个,以此类推。下面是我自己写的代码,很长。。。
void listArray::delByItem(int val)
{
listNode* t = head,*p=head;
while (t)
{
if (t->element == val)
{
if (p == t)//当删除的结点为头结点时
{
head = head->next;
delete(p);
p = head;
t = head;
}
else
{
p->next = t->next;
delete(t);
t = p->next;
}
}
else
{
p = t;
t = t->next;
}
}
}
这个是人家写的。。。c++STL库中list容器的删除对应值结点的实现:
void List::removeData(int data)
{
for(Node** cur = &m_head;*cur;)
{
Node* entry = *cur;
if(entry->data == data)
{
*cur=entry->next;
free(entry);
}
else
cur=&entry->next;
}
}
链表的删除:
与前面的打印类似,遍历链表所有结点,然后删除。
void listArray::del()
{
listNode* t = head;
while (head != NULL)
{
t = t->next;
delete(head);
head = t;
}
}