线性表分为顺序表和链表,顺序表可以理解成“数组”。定义数组时会从内存中划分一段连续地址存放,而链表则由若干个结点组成,且存储位置不连续。链表的两个结点之间一般通过一个指针来从一个结点指向另一个结点,因此链表的结点一般由两部分组成,即指针域和数据域:
struct node{
typename data;//数据域
node* next;//指针域
};
数据域存放结点要存储的数据,而指针域指向下一个结点的地址。链表以是否存在头结点分为带头结点的链表和不带头结点的链表。头结点,一般称为head,且数据域data不存放任何内容,指针域next指向第一个数据域有内容的结点(第一个结点)。最后一个结点的next指向NULL,表示一条链表的结尾。
C语言中使用malloc,C++中使用new(推荐)。
malloc函数是C语言中stdlib.h头文件下用于申请动态内存的函数,返回类型是申请的同变量类型的指针。
typename* p = (typename*)malloc(sizeof(typename));
以申请一个int型变量和一个node型结构体变量为例:
int* p =(int*)malloc(sizeof(int));
node* p =(node*)malloc(sizeof(node));
当申请失败后返回空指针NULL。
new运算符是C++中用于申请动态内存的函数,返回类型是申请的同变量类型的指针:
typename* p = new typename;
同样以申请一个int型变量和一个node型结构体变量为例:
int* p = new int;
node* p = new node;
只需要“new+类型名”即可分配一块该类型的内存空间,并返回一个对应类型的指针。如果申请失败,会启动C++异常机制处理。(失败是因为申请了较大的动态数组)
内存泄漏是指使用malloc或new开辟出来的内存空间在使用过后没有释放。使用后必须释放空间。
free函数对应malloc函数,同样在stdlib头文件下。使用方法只需要在free的参数中填写需要释放的内存空间的指针变量:free(p);
delete运算符对应new运算符,使用方法同上:delete(p);
free函数和malloc函数,delete运算符和new运算符都必须成对出现。
上述new已经创建了若干个零散的结点,只要把每个结点的next指针指向下一个结点的地址即可形成链表。使用for循环建立需要的链表。
#include
using namespace std;
struct node{
int data;
node* next;
};
//创建链表
node* create(int array[]){
node* p,*pre,*head;//pre保存当前结点的前驱结点,head为头结点
head = new node;//创建头结点
head->data = NULL;
pre = head;//记录pre = head
for(int i =0;i<5;i++){//遍历array数组
p =new node;
p->data = array[i];
p->next = NULL;
pre->next = p;
pre = p;
}
return head;//返回头结点指针
}
int main(){
int array[] = {5,3,6,1,2};
node* L = create(array);
L = L->next;//从第一个结点开始有数据域
while(L != NULL){
printf("%d",L->data);
L = L->next;
}
return 0;
}
如果已经有了一条链表,那么如何查找其中是否有给定的元素x?只需从第一个结点开始,不断判断当前结点的数据域是否等于x,雨果等于,那么就给计数器count++,这样当到达链表结尾时,count的值就是链表中元素x的个数。
//在以head为头结点的链表上计数元素x的个数
int search(node* head,int x){
int count = 0;//计数器
node* p = head->next;//从第一个结点开始
while(p != NULL){
if(p->data == x) count++;
p = p->next;
}
return count;
}
思路就是每次把最小值放在最后一个。
比如除头结点的五个5个节点;
开始每个节点分别赋值为 5,3,1,4,2;
第一次外循环后结果变为 5,3,4,2,1;
第二次外循环后结果变为 5,3,4,1,2;
第三次外循环后结果变为 5,4,1,2,3;
第四次外循环后结果变为 5,1,2,3,4;
第五次外循环后结果变为 1,2,3,4,5;
void SortList(Node *pHead)//传来一个头节点指针
{
Node *q,*tail=pHead->next,*t;
while(tail->next!=NULL)
{
tail=tail->next;//找到尾巴
}
int min,i,j;
for(i=0;i<ListLength(pHead);i++)//ListLength(pHead)为单链表长度(不包括头节点) 的函数
{
q=pHead;
t=q;
min=q->next->data;
for(j=0;j<ListLength(pHead)-i;j++)
{
if(min > q->next->data)//找到最小值,用 t 记录最小值节点上一个节点
{
min=q->next->data;
t=q;
}
q=q->next;
}
if(tail==t->next)//如果最小值节点为尾节点,无需调换
{
continue;
}
else
{
tail->next=t->next;
t->next=t->next->next;
tail=tail->next;
tail->next=NULL;
}
}
}
在第i个位置插入元素x是指:在插入完成后,第i个位置的元素就是x。
//将x插入以head为头结点的链表的第pos个位置
void insert(node* head,int pos,int x){
node* p =head;
for(int i =0;i<pos-1;i++){
p = p->next;//pos-1是为了到达pos的前一个位置结点
}
node* q = new node;//新建结点
q->data = x;
q->next = p->next; //新结点的下一个结点指向原先插入的位置
p->next = q;//前一个位置的结点指向新结点
}
删除元素是指删除链表上所有值为给定的数x。
//删除以head为头结点的链表中所有数据域为x的结点
void del(node* head,int x){
node* p = head->next;
node* pre = head;
while(p != NULL){
if(p->data == x){
pre->next = p->next;
delete(p);
p = pre->next;
}else{
pre = p;
p = p->next;
}
}
}
上面讲的都是动态链表,即需要指针来建立结点之间的连接关系。如果有些问题中结点的地址是比较小的整数(例如五位数的地址),这样就没必要去建立动态链表,而使用方便的静态链表。
静态链表的实现原理是hash,即通过建立一个结构体数组,并令数组的下标直接表示结点的地址,来达到直接访问数组中的元素就能访问结点的效果。另外,静态链表不需要头结点。
struct Node{
typename data;//数据域
int next;//指针域
}node[size];
在使用静态链表时,注意尽量保证结构体类型名和结构体变量名不相同。