C++小虾米的第一篇文章–数据结构单链表
1.线性表
线性表有两种结构,顺序存储结构和顺序存储结构,顺序存储结构的线性表理解起来很简单。
对于顺序存储,我们首先能想到的数组,而顺序存储的线性表就是将结构体+数组的这一种组合来实现。例如:
#define MAXSIZE 20
typedef int ElemType;
typedef struct
{
ElemType data[MAXSIZE];
int length;
}sqList;
使用的时候只需要创建一个sqList变量即可,顺序存储结构方便我们进行查找和修改操作,只需要知道它的下标就可以定位到指定元素。然而,对于顺序存储,如果我们需要插入或者删除元素,效率将是极低的。 例如,如果需要增加一个元素,为了不破坏顺序连接性,选好插入位置之后,必须将该位置之后所有的元素都向后移动一位,这就增加了时间复杂度,具体不谈。
线性表还有一种结构:链式存储结构。链式存储结构结合了结构体和指针,使得元素和元素之间通过指针变量进行联系,你不一定要在我旁边,我知道你在哪就行。
typedef struct Node
{
ElemType data; //数据域
Struct Node* Next; //指针域
}Node;
链表有一个特殊的东西,叫做指针,这个指针指向了下一个元素,所以遍历的时候,我们只需要知道起始指针便可一步步往下遍历,找到最后一元素。约定了两个指针:head和tail,其中tail的next指针指向NULL。
2.新的操作
原始的增删查改的操作在链表中行不通,我们重点要关注指针这个东西,指针和地址紧密联系,所以链表的增删查改操作要和指针联系起来,总结起来无非几句话:
1.插入操作:遍历到插入位置,前指针的next指向要插入的元素,插入元素的next指向前指针的next;
2.删除操作:遍历到删除位置,前指针的next指向删除位置的next,注意一下,删除的元素要free一下,释放空间!!!
3.查找操作:不多说,遍历比较;
4.修改操作:来个计数器,遍历到指定位置,修改数据域。
3.代码的分块实现
3.1 头文件.h
本文用的简单的C++类写的一个链表,将插入删除等一系列方法放到Clinklist这个类中。
头文件代码如下:
//文件名为Clinklist.h。
#pragma once
#include <stdlib.h>
#include <string.h>
#include <iostream>
typedef struct node
{
char data;
//数据域
struct node *ptr;
//指针域
}Node;
class Clinklist
{
public:
Clinklist(void);
//构造函数:初始化变量
virtual ~Clinklist(void);
//虚析构函数:本文用作free掉malloc的内存
private:
Node *phead,*ptail;
//定义头指针和尾指针
public:
//void sethead( Node *HEAD ){ phead = HEAD; };
//Node* gethead(){ return phead;};
void menu();
//做了一个菜单
void pushfront();
//初始化链表,向前插入数据
void pushback();
//初始化链表,向后插入数据
void deletenode(void);
//删除操作
void insert(int loc,int data);
//插入操作:形参为位置和数据
int getlength();
//获取链表长度
void print();
//打印操作
void freem();
//free内存
};
3.2函数实现.cpp
接下来在cpp文件中实现头文件中的函数:
#include "Clinklist.h"
Clinklist::Clinklist(void)
//构造函数
{
phead = NULL;
ptail = NULL;
}
Clinklist::~Clinklist(void)
//析构函数
{
freem();
}
void Clinklist::pushback()
//初始化,向后插入操作
{
char c;
std::cin >>c;
if( !phead )
//当phead = NULL时,申请空间给头指针,头指针此时等于尾指针,将尾指针下一指向置为NULL。
{
phead = (Node*)malloc(sizeof(Node));
phead -> data = c;
ptail = phead;
ptail -> ptr = NULL;
}
else
{
Node* p= (Node*)malloc(sizeof(Node));
p -> data = c;
ptail -> ptr = p;
ptail = p;
ptail -> ptr = NULL;
}
}
void Clinklist::pushfront()
//初始化,向前插入操作
{
char c;
std::cin >> c;
if( !phead )
{
phead = (Node*)malloc(sizeof(Node));
phead -> data = c;
ptail = phead;
ptail -> ptr = NULL;
}
//这里刚学的时候有点疑问,对phead赋值的话会不会改变原先的值?改变了地址,其实地址指向的东西没有变化。
else
{
Node *p= (Node*)malloc(sizeof(Node));
p -> data = c;
p -> ptr = phead;
phead = p;
}
}
void Clinklist::freem()
//释放空间很好理解,只要增加一个中间变量,从head开始,将下一个赋值给这个中间变量,同时释放当下变量,直到遍历到NULL停止。
{
while (phead)
{
Node *p = phead;
phead = phead -> ptr;
free(p);
}
}
void Clinklist::print()
//打印不必多说,遍历
{
Node *p = phead;
while( p != NULL)
{
std::cout << p->data << std::endl;
p = p -> ptr;
}
}
void Clinklist::deletenode(void)
//删除操作需要特别注意删除元素的位置
{
Node *p = phead,*p2 = phead;
std::cout <<"输入需要删除的节点数据"<<std::endl;
char c ;
std::cin >> c;
while( p )
{
if( p->data == c)
//如果找到了这个元素,停止while
{
break;
}
p2 = p;
//p2指向这个节点的前一个节点
p = p -> ptr;
//p为当前节点
}
if( p == NULL )
//删除失败提醒
std::cout << "没有那一个节点"<<std::endl;
else
{
//需要注意这里的大括号,一直延伸到
if( p == phead )
{
phead = p->ptr;
free(p);
}
else
{
p2 -> ptr = p-> ptr;
free(p);
}
}
}
int Clinklist::getlength()
//遍历,设置个计数器,返回一下
{
Node *p = phead;
int i = 0;
while( p )
{
++i;
p = p -> ptr;
}
return i;
}
void Clinklist::insert(int loc,int data)
//插入操作也需要注意一下插入的位置,本文做的比较不严谨,没有做前插后插
{
Node *p = phead;
int i = 1;
int len = getlength();
Node *p2 = (Node *)malloc(sizeof(Node));
p -> data = data;
if ( loc == 0)
//如果loc为0,则将数据插入到头指针
{
p2 -> ptr = phead;
phead = p2;
}
else if (loc == len)
//如果loc等于链表长度,则将其插入尾指针
{
ptail-> ptr = p2;
ptail = p2;
ptail -> ptr = NULL;
}
else
//其他情况做个计数器,遍历到指定位置新建节点,连接前后就好
{
while( p )
{
p = p -> ptr;
i++;
if (loc == i)
{
p2 -> ptr = p -> ptr;
p -> ptr = p2;
break;
}
}}
}
void Clinklist::menu()
//做了个小小的菜单
{
std::cout<<" Main Menu\n";
std::cout<<"----------------------------------------------\n";
std::cout<<" (1) ------初始化:往前插入数据\n";
std::cout<<" (2) ------初始化:往后插入数据\n";
std::cout<<" (3) ------插入操作\n";
std::cout<<" (4) ------在表中删除数据\n";
std::cout<<" (5) ------获取表长\n";
std::cout<<" (6) ------打印\n";
std::cout<<" (7)------exit system\n";
std::cout<<"----------------------------------------------\n";
}
3.3主函数.cpp
接下来就是主函数了:
#include "Clinklist.h"
int main()
{
Clinklist list;
list.sethead(NULL);
int loc,data;
while(1) //做一个循环
{
list.menu();
printf("输入命令:\n");
int ic ;
std::cin >> ic;
switch(ic) //再来一个开关
{
case 1:
{
printf("Please input your info: ");
list.pushfront();
break;
}
case 2:
{
printf("Please input your info: ");
list.pushback();
break;
}
case 3:
{
std::cout <<"输入位置和需要插入的数据"<<std::endl;
std::cin >> loc >> data;
list.insert(loc,data);
break;
}
case 4:
{
list.deletenode();
break;
}
case 5:
{
printf("the length of list is %d\n",list.getlength());
break;
}
case 6:
{
list.print();
break;
}
case 7:
{
//list.freeMemory();
exit(0);
}
}
}
return 0;
}
4.总结
作为数据结构的入门,链表发挥了它及其重要的作用,本文只介绍了单链表,对于链表的学习也是刚刚起步,关于多链表和循环链表,相信也只是在指针域上做做手脚,分析一下也并没有很难,只是要理解一下这种思想,链表在数据结构中处处体现,栈与队列,树等也将会使用到链表的知识,希望多多交流,希望文章有所帮助。