[data structure] Chapter II linear table

[data structure] Chapter II linear table

1. Basic concepts

Logical structure of linear table: linear table is a finite sequence of n data elements of the same type, and there is a one-to-one relationship between data elements, that is, except that the first element has no direct precursor and the last element has no direct successor, each data element has at most one direct precursor and one direct successor. There is a one-to-one relationship between elements.

(1) Sequential storage of linear tables
  1. Sequential storage structure of linear table: a group of storage units with continuous address are used to store all elements in the linear table in turn, so that the adjacent data elements in the logical structure of the linear table are stored in the adjacent physical storage units, that is to say, the logical adjacent relationship is reflected by the adjacent relationship of the physical storage of the data elements. Linear tables with sequential storage structure are usually called sequential tables.
  2. Suppose there are n elements in the linear table, each element occupies k units, the address of the first element is the base address of loc(a1), then the address of the ith element is loc(ai)=loc(a1)+(i-1) × k

    50. Last can get the subscript of the last element in the sequence table, and L.last+1 is the length of the sequence table
  3. The advantages of linear table sequential representation are:
    (1) There is no need to add additional storage space to represent the logical relationship between nodes (because the physical location of logically adjacent elements is also adjacent);
    (2) Any element in the table can be accessed at random.
    Its disadvantages are:
    (1) It is not convenient to insert or delete. Except for the position of the tail of the table, a large number of elements must be moved to insert or delete in other positions of the table, which is inefficient;
    (2) Because the sequential table requires continuous storage space, storage allocation can only be done statically in advance.
(2) Chain storage of linear table

Linked list is a set of arbitrary storage units to store the nodes of linear table. This set of storage units can be continuous, discontinuous, or even scattered in any location of memory.
(1) Single chain table
The single chain table consists of two fields: the data field is used to store the value of the node; the pointer field is used to store the direct successor address (or location) of the data element.
Each node of a linked list has only one pointer field, so it is also called a single linked list.

Since the storage address of each node in the single chain table is stored in the pointer field of its predecessor node, and the first node has no predecessor, a header pointer H should be set to point to the first node. At the same time, since the last node in the table has no direct successor, the pointer field of the last node in the linear table is specified as "null".
Sometimes for the convenience of operation, a header node can be attached before the first node of the single chain table. At this time, the head pointer no longer points to the first node in the table, but to the head node. If the linear table is empty, the pointer field of the header node is empty.
(2) Circular list
Circular linked list is another form of single linked list, which is a linked list with head and tail. Its characteristic is to change the pointer field of the last node of the single chain table from NULL to the first node of the head node or the linear table, so as to get the circular list of single chain form, which is called the circular single chain table. Similarly, there are circular lists of multiple chains.
The loop condition in the algorithm is not p!=NULL or P-1 > NEXT1 = null, but p! =L or P - > next! = L.

Attaching a tail pointer to a circular single chain table sometimes makes the operation easier than attaching a head pointer. For example, in the cyclic single chain table represented by the head pointer, the time complexity of finding the starting node a is O (1), but to find the terminal node an, it is necessary to
The time complexity of traversing the whole linked list from the beginning of pointer is O (n). If the tail pointer rear is used to represent the circular single chain table, their storage locations are rear - > next - > next and rear respectively, and the search time complexity is O (1). Therefore, tail pointer is often used to represent cyclic single chain table in practice.
(3) Double linked list
The emergence of cyclic single chain table, although it can be realized that the precursor nodes can be found from any node along the chain, but the time consumption is O (n). In each node of the single chain table, a pointer domain prior is added to its precursor. In this way, there are two chains with different directions in the chain list, which we can call double linked list.


(4) Static list
All the above mentioned linked lists are realized by pointers. The allocation and recovery of node space in the linked list are realized dynamically by the standard functions malloc and free provided by the system, so it is called dynamic linked list. However, some high-level languages do not provide the data type of "pointer". At this time, if you want to use the linked list as the storage structure, you must use "cursor" to simulate the pointer. The single chain table implemented by cursor indicator is called static single chain table.

2. algorithm

(1) Sequential storage of linear tables

Sequential storage structure of linear table:

#define MAXSIZE 100
typedef struct
{
	ElemType elem[MAXSIZE];
	int last;
}SeqList;
  1. Order table search by content
    Algorithm idea: search from the first element, equal to e, return the sequence number, no equal to e, return - 1 if the search fails;
    Time complexity is O(n)
int Locate(SeqList L, ElemType e)
{
	int i = 0;
	while((i<=L.last)&&(L.elem[i]!=e))
		i++;
	if(i<=L.last)
		return i+1;
	else
		return -1;
}
  1. Insertion of linear table
    Algorithmic idea: it is necessary to change n in the original table The nodes on i move backward one bit in turn, vacate the ith position, and insert. Insert end directly if i=n+1
    Average number of moving elements: n/2
int InsList(SeqList *L, int i, ElemType e)
{
	if(i<1||i>L->last+2)//The subscript of the ith element is i-1, so i < 1
		return -1;
	if(L->last==MAXSIZE-1)
		return -1;
	for(int k=L->last;k>=i-1;k--)
		L->elem[k++]=L->elem[k];
	L->elem[i-1]=e;
	L->last++;
	return 1;
}
  1. Deletion of linear table
    Algorithm idea: when the i-th element is deleted, i+1 n move one bit forward
    Average number of moving elements: (n-1) / 2
int DelList(SeqList *L ,int i, ElemType e)
{
	if(i<1||i>L->last+1)
		return -1;
	*e=L->elem[i-1];
	for(int k=i;k<L->last+1;k++)
		L->elem[k-1]=L->elem[k];
	L->last--;
	return 1;
}
  1. Example 1
    There are two order tables LA and LB, whose elements are all non decreasing order. Write an algorithm to combine them into a order table LC, which requires LC to be non decreasing order. For example, LA=(2,2,3), LB=(1,3,3,4), then LC=(1,2,2,3,3,3,4)
    Algorithm idea: set the pointer i,j to judge the size of LA[i] and LB[j], and put the small ones into LC until LA(LB) is scanned, and put the elements in LB(LA) into LC
    Time complexity O (LA - > last + LB - > last)
void merge(SeqList *LA,SeqList *LB,SeqList *LC)
{
	int i,j,k;
	i=0;j=0,k=0;
	while(i<=LA->last&&j<=LB->last)
	{
		if(LA->elem[i]<=LB->elem[j])
		{
			LC->elem[k]=LA->elem[i];
			i++;k++;
		}
		else
		{
			LC->elem[k]=LB->elem[j];
			j++;k++;
		}
	}
	while(i<=LA->last)
	{
		LC->elem[k]=LA->elem[i];
		i++;k++;
	}
	while(j<=LB->last)
	{
		LC->elem[k]=LB->elem[j];
		j++;k++;
	}
	LC->last=LA->last+LB->last+1;
}
  1. Example 2
    Given that the data element type in the order table L is int, it is adjusted to the left and right parts. The left part is odd and the right part is even. The required time complexity is O(n) and space complexity is O(1)
    Algorithm idea: set the pointer I = 0, j = L - > last, when L[i] is even and L[j] is odd, exchange, otherwise i++,j –
void AdjustSqlist(SeqList *L)
{
	int i,j,temp;
	i=0;j=L->last;
	while(i<j)
	{
		while(L->elem[i]%2!=0)
			i++;
		while(L->elem[j]%2==0)
			j--;
		if(i<j)
		{
			temp=L->elem[i];
			L->elem[i]=L->elem[j];
			L->elem[j]=temp;
		}
	}
}
(2) Chain storage of linear table

Single chain table storage structure:

typedef struct Node
{	ElemType data;
	struct Node* next;
}Node,*LinkList;	//*LinkList is a structure pointer type

(1) Single chain table

  1. Initialize single chain table
    L is the pointer to the head node of the single chain table, which is used to receive the address of the head pointer variable of the single chain table to be initialized in the main program, * l is equivalent to the head pointer variable of the single chain table to be initialized in the main program.
InitList(LinkList *L)
{
	*L=(Linklist)malloc(sizeof(Node));
	(*L)->next=NULL;
}
  1. Establish single chain table
    a. Table building by head insertion method
    Algorithm description: after inserting the new node into the head node of the current linked list, the logical order of the single linked list is opposite to the input order
void CreateFromHead(LinkList L)
{
	Node *s;
	char c;
	int flag = 1;
	while(flag)
	{
		c=getchar();
		if(c!='$')
		{
			s=(Node*)malloc(sizeof(Node));
			s->data=c;
			s->next=L->next;
			L->next=s;
		}
		else flag = 0;
	}
}

b. Table construction by tail insertion method
Algorithm idea: insert the new node into the end of the current linked list, and the end pointer r needs to point to the end of the list.

void CreateFromTail(LinkList L)
{
	Node *r,*s;
	char c;
	int flag = 1;
	r=L;//r points to the end of the current linked list
	while(flag)
	{
		c=getchar();
		if(c!='$')
		{
			s=(Node*)malloc(sizeof(Node));
			s->data=c;
			r->next=s;
			r=s;
		}
		else 
		{
			flag = 0;
			r->next = NULL;
		}
	}
}
  1. lookup
    The time complexity of both algorithms is O (n)
    a. Find by sequence number
    Algorithm idea: look up the i-th node in the table, start from the head pointer, use j as the counter, when i = j, find the node.
Node* Get(LinkList L ,int i)
{
	if(i<=0) return NULL;
	int j = 0;
	Node *p;
	p=L;
	while((j<i)&&(p->next!=NULL))
	{
		p=p->next;
		j++;
	}	
	if(i==j) return p;
	else  return -1;
}

b. search by value
Algorithm idea: check whether there is a node whose node value is e one by one from the beginning. If there is a node whose storage location is returned, NULL will be returned

Node * Locate(LinkList L ,ElemType key)
{
	Node *p;
	p=L->next;	//Start with the first element in the table
	int i=1;
	while((p!=NULL)&&(p->data!=key))
	{
		p=p->next;
		i++;
	}
	if(p!=NULL)
		return i;
	else return -1;
}
  1. Insert operation
    Algorithm idea: insert e before i position. 1. Find that i-1 pre points to i-1, 2. Apply for new node s data field as e 3. Insert the chain.
    Note: if there are m nodes in a single chain table, if I < m + 1, the head insertion method is used, and if i =m+1, the nodes are inserted at the end of the chain.
void InsList(LinkList L, int i ,ElemType e)
{
	if(i<=0) printf('Insertion position error');
	Node *pre,*s;
	pre = L;
	int j=0;
	while(pre!=NULL&&j<i-1)
	{	
		pre=pre->next;
		j++
	}
	if(pre==NULL) printf('Unreasonable insertion position');
	s=(Node*)malloc(sizeof(Node));
	s->data=e;
	s->next = pre->next;
	pre->next=s;
}
  1. Delete operation
    Algorithm idea: delete the ith node element e. 1. Find that i-1 p points to i-1. 2. Delete i and free up space.
int DelList(LinkList L ,int i ,ElemType *e)
{
	if(i<=0) printf('Delete location is unreasonable');
	Node *p, *s;
	int j = 0;
	p=L;
	while(p->next!=NULL&&j<i-1)	//This is different from forward interpolation
	{
		p=p->next;
		j++
	}
	if(p->next==NULL) printf('Delete location is unreasonable');
	s=p->next;
	*e = s->data;
	p->next=s->next;
	free(s);
}

Note: (P - > next! = null) is different from the pre insertion method, because when inserting, there are m+1 (m-long chain list) i=m+1 at the insertion position, indicating that the element is inserted at the end of the chain. When deleting, there are only m legal positions. If the condition is the same as that of insert loop, the pointer will be null.

  1. Example 1
    There are two incremental sequence lists LA and lb. use the new table LC to merge the existing LA and LB tables without additional application space. Such as LA(2,2,3),LB(1,3,3,4),LC(1,2,2,3,3,3,4)
    Algorithm idea: reconstruct the linear relationship between new elements by changing the next field of the node.
LinkList MergeLinkList(LinkList LA, LinkList LB)
{
	Node *pa,*pb,*pc;
	LinkList LC;
	pa=LA->next; pb=LB->next;
	LC=LA;
	LC->next=NULL;
	pc=LC;	//pc now points to the head node, pc - > next selects the smaller value from pa,pb.
	while(pa!=NULL&&pb!=NULL)
	{
		if(pa->data <= pb->data)
		{
			pc->next = pa;
			pc=pa;
			pa=pa->next;
		} 
		else
		{
			pc->next = pb;
			pc=pb;
			pb=pb->next;
		}
	}
	if(pa==NULL) pc->next=pb;
	else pc->next=pa;
	free(LB);
}
  1. Example 2
    Find the difference between two sets. A-B contains all elements belonging to a but not b
    Algorithm idea: each element E in A is searched in the linked list of set B. if there is the same element as e, it will be deleted from A.
void Difference(LinkList LA, LinkList LB)
{
	Node *pre,*pa,*pb,*s;
	pa=LA->next;
	pre=LA;
	while(pa!=NULL)
	{
		pb=LB->next;
		while(pb!=NULL&&pa->data!=pb->data)  pb=pb->next;
		if(pb)
		{
			s=pa;
			pre->next=pa->next;
			pa=pa->next;
			free(pa);
		}
		else
		{
			pre=pa;
			pa=pa->next;
		}
	}
}

(2) Circular list

  1. Initialize circular single chain table
InitCLinkList(LinkList *CL)
{
	*CL = (LinkList)malloc(sizeof(Node));
	(*CL)->next=*CL;
}
  1. Establish circular single chain table
void CreateCLinkList(LinkList CL)
{
	Node *s,*r;
	r=CL->next;
	char c;
	int flag = 1;
	while(flag)
	{
		c=getchat();
		if(c!='$')
		{
			s=(Node*)malloc(sizeof(Node));
			s->data=c;
			r->next=s;
			r=s;
		}
		flag=0;
		r->next=CL;//The next of the last node points to the head node
	}
}
  1. Example 1
    Merge of circular single chain table
    Algorithm idea: set the tail pointer representation, directly modify the tail pointer field without loop traversal of the tail node, and its execution time is O (1)
LinkList merge(LinkList RA, LinkList RB)
{
	Node *p;
	p=RA->next;	//Save the head node of RA
	RA->next=RB->next->next;	//After the start node of RB is connected to the end node of RA
	free(RB->next);	//Release RB's head node
	RB->next=p;	//After the head node of RA is connected to the terminal node of RB
	return RB
}

(3) Double linked list
Double linked list structure definition:

typedef struct DNode
{	ElemType data;
	struct DNode *prior,*next;
}DNode,*DoubleList;
  1. Fork operation
    Algorithm idea: insert a new node before i position. Judge the validity of i position, find the ith node, and point to it
int DlinkIns(DoubleList L ,int i ,ElemType e)
{
	DNode *s,*p;
	...//Judge the legitimacy of i position
	...//The pointer from the head node p points to the i-th position element
	s=(DNode*)malloc(sizeof(DNode));
	s->data=e;
	s->prior=p->prior;
	p->prior->next=s;
	s->next=p;
	p->prior=s;
}
  1. Delete operation
    Algorithm idea: delete the node in i position. Judge the validity of i position, find the ith node, and point to it
int DlinkDel(DoubleList L, int i, ElemType *e)
{
	DNode *p;
	...//Judge the legitimacy of i position
	...//Locate i, p points to i
	p->prior->next=p->next;
	p->next->prior=p->prior;
	free(p);
}
Published 9 original articles, won praise 0, visited 130
Private letter follow

Posted on Fri, 14 Feb 2020 08:22:36 -0500 by Sangre