线性表
- 相同数据类型
- 有限
- 序列
基础定义
使用c/c++通用的语法定义:
1. 顺序表:用数组实现
int A[MaxSize]; //定义顺序表,一次性开辟好空间。
int length = 0; //顺序表的数据长度,使用时再修改长度。
特点:顺序存储可以根据下标快速访问元素;插入、删除要移动大量的数据、一次性开辟好全部空间等。
2. 链表:用结构体实现。
- 单链表:只有一个指针:
typedef struct LNode{
int data; //数据项
struct LNode* next; //链表指针向后
}LNode;
- 双链表:两个指针:
typedef struct DLNode{
int data; //数据项
struct DLNode* next; //链表指针向后
struct DLNode* prior; //链表指针向前
}DLNode;
链表的使用
单链表示意图:
//创建结点
LNode *L;
L=(LNode*)ma11oc(sizeof(LNode));
//同理创建ABCD结点后,用next指针进行连接。
A->next = B;
B->next = C;
C->next = D;
单链表:
单链表的结构示意图以及判空条件
其中头结点是没有数据的节点,用来更方便地操作链表。
双链表的使用:
双链表的结构示意图以及判空条件:
循环链表
静态链表
typedef struct {
int data; //数据内容。
int next; //使用整形数据保存地址,相当于指针类型。
}SLNode;
示意图如下:
链表的操作
插入删除操作
单链表:
插入操作:注意顺序
删除操作:使用free()函数:
注意事项:
双链表:
删除操作:
建表
顺序表建立:
int A[MaxSize];
int length;
int createlist(int A[], int &length){
cin>>length;
if (length > MaxSize)
return 0
for(int i=0; i<length; ++i)
cin>>A[i];
return 1
}
链表的建立:(插入操作)
- 尾部插入法:
void createLinkListR(LNode *&head){
head = (LNode*)malloc(sizeof(LNode)); //创建头结点
head->next = NULL;
LNode *p = NULL, *r = head;//创建两个结点,p用来为新节点服务,r用来暂时保存前一个结点的指向
int n; //数据结点个数
cin>>n;
for (int i = 0; i < n; ++i){
p = (LNode*)malloc(sizeof(lNode)); //创建结点
p->next= NULL;
cin>>p->data;
p->next = r->next; //创建的结点进行连接,可略,用于链表中部插入结点
r->next = p; //
r = p; //r始终指向当前最后的结点
}
}
- 头部插入法
void createLinkListH(LNode *&head){
head = (LNode*)malloc(sizeof(LNode)); //创建头结点
head->next = NULL;
LNode *p = NULL;//创建结点,p用来为新节点服务
int n; //数据结点个数
cin>>n;
for (int i = 0; i < n; ++i){
p = (LNode*)malloc(sizeof(lNode)); //创建结点
p->next= NULL;
cin>>p->data;
p->next = head->next; //创建的结点进行连接,可略,用于链表中部插入结点
head->next = p;
}
}
!!!!!使用头部插入法的顺序结果应该是逆序哦。
应用例题:
键盘输入n个英文字母,输入格式为n、C1、C2、…、Cn,其中表示字母的个数。请编程以这些输入数据建立一个单链表,并要求将字母不重复的存入链表。
输入一个单词,扫描其在链表中是否岀现,如果岀现,就什么都不做;否则,根据这个单词构造结点插入链表中。
//头部插入发代码实现:
void createLinkListNoSameElem(LNode *&head){
head = (LNode*)malloc(sizeof(LNode)); //创建头结点
head->next = NULL;
LNode *p = NULL;//创建结点,p用来为新节点服务
int n; //数据结点个数
char ch;
cin>>n;
for (int i = 0; i < n; ++i){
cin>>ch;
p = head->next;
while(p != NULL){
if(p->data == ch)
break;
p = p->next;
}
if(p == NULL){
p = (LNode*)malloc(sizeof(lNode)); //创建结点
p->data = ch;
p->next = head->next;
head->next = p;
}
}
}
表的逆置
- 顺序表(数组):
//偶数奇数通用
for (int i = left, j = right; i < j; ++i, --j){
temp = a[i];
a[i] = a[j];
a[j] = temp;
}
- 链表的逆置:
while(p->next !=q){
t = p->next;
p->next = t->next;
t->next = q->next;
q->next = t;
}
例题:
将一长度为n的数组的前端k(k<n)个元素逆序后移动到数组后端,要求原数组中数据不丢失;
void reverse(int a[], int left, int right, int k){
int temp;
for(int i = left, j = right, i < left + k && i < j; ++i, --j){
temp = a[i];
a[i] = a[j];
a[j] = temp;
}
}
将一长度为n的数组的前端k(k<n)个元素保持原序移动到数组后端,要求原数组中数据不丢失;
思路:把要交换的先逆置一下,再交换。
void moveToEnd(int a[]; int n; int k){
reverse(a, 0, k-1, k);
reverse(a, 0, n-1, k);
}
取最值
顺序表:
int max = a[0];
int maxIdx = 0;
for (int i = 0; i < n; ++i){
if (max < a[i]){
max = a[i];
maxIdx = i;
}
}
链表:
LNode *p, *q;
int max = head->next->data;
q = p = head->next; //q用于结点的遍历的移动
while(p != NULL){
if (max < p->data){
max = p->data;
q = p;
}
p = p->next;
}
例题:
双链表非空,由head指针指出,结点结构为{llink, data, rlink},请设计一个将结点数据域data值最大的那个结点(最大值结点只有一个)移动到链表最前边的算法,要求不得申请新结点空间。
首先双链表定义:
typedef struct DLNode{
int data; //数据项
struct DLNode* llink; //链表指针
struct DLNode* rlink; //链表指针
}DLNode;
void maxFirst(DLNode *head){
DLNode *p = head->rlink, *q =p;
int max = p->data;
//找最值
while(p != NULL){
if (max < p->data){
max = p->data;
q = p;
}
p = p->rlink;
}
}
//删除
DLNode *l = q->llink, *r = q->rlink;
l->rlink = r;
if (r != NULL)
r ->llink = l;
//插入最值结点
q->llink = head;
q->rlink = head->rlink;
head->rlink = q;
q->rlink->llink = q;
例题:
假定采用带头结点的单链表保存单词,当两个单词有相同的后缀时,则可共享相同的后缀存储空间,如," loading”和" being"的存储映像如下图所示。
设st1和st2分别指向两个单词所在单链表的头结点,链表结点结构为{data,next},请设计一个时间上尽可能高效的算法,找岀由str1和str2所指向两个链表共同后缀的起始位置(如图中字符诟在结点的位置p)
要求:
1)给出算法的基本设计思想
2)根据设计思想,采用C或C++或JAVA语音描述算法,关键之处给出注释。
3)说明你所设计算法的时间复杂度。
思路:用长度的差值入手破解。
1)分别求出str1和st2所指的两个链表的长度m和n
2)令指针p、q分别指向st1和st2的头结点,若m>=n,则使p指向链表中的第m-n+1个结点若m<n,则使q指向链表中的第n-m+1个结点;即使指针ρ和q所指的结点到表尾的长度相等
3)将指针p和q同步向后移动,并判断它们是否指向同一结点。若p和q指向同一结点,则该点即为所求的共同后缀的起始位置。
划分问题
· 枢轴
归并问题
(合成大西瓜)
- 把两个数组合并成一个,按照从小到大的顺序。
用数组实现:
void mergeArray(int a[], int m, int b[], int n, int c[]){
int i = 0, j = 0; //作为a、b的遍历的下标
int k = 0; //作为c的遍历下标
while (i < m && j < n){
//相同长度部分的合并
if (a[i] <b[j])
c[k++]=a[i++];//c[k]=a[i];k++;i++;
else
c[k++]=b[j++];
//较长的数组的处理
while (i < m)
c[k++]=a[i++];
while (j < n)
c[k++]=b[j++];
}
}
- 用链表的实现:
void merge(LNode *A, LNode *B, LNode *&C){
LNode *p = A->next; //取A,B的头结点
LNode *q = B->next;
LNode *r; //标记尾部位置的指针
C = A; //任意取一个头结点
C->next = NULL; //结点指针初始化
free(B);
r = C;
//数据处理
while(p != NULL &&q != NULL){
if(p->data <= q->data){
r->next = p;
p = p->next;
r = r->next;
}
else{
r->next = q;
q= q->next;
r =r->next;
}
}
if (p!=NULL) r->next = p;
if (q!=NULL) r->next = q;
}
法2:逆序归并:
void merge(LNode *A, LNode *B, LNode *&C){
LNode *p = A->next; //取A,B的头结点
LNode *q = B->next;
LNode *s; //标记尾部位置的指针
C = A; //任意取一个头结点
C->next = NULL; //结点指针初始化
free(B);
//r = C;
//数据处理
while(p != NULL && q != NULL){
if(p->data <= q->data){
s = p;
p = p->next;
s->next = C->next;
C->next = s;
}
else{
s = q;
p = q->next;
s->next = C->next;
C->next = s;
}
}
while(p!=NULL){
s = p;
p = p->next;
s->next = C->next;
C->next = s;
}
while(q!=NULL){
s = q;
p = q->next;
s->next = C->next;
C->next = s;
}
}