Fifteen operations for dynamically allocated ordered linear tables-implemented in C Language
Linear table definition: it is the most common and simple data structure. It is an ordered sequence of n data elements in length. A linear table with a large number of records is called a file record: In a slightly complex linear table, data elements are composed of several data items. In this case, a data element is called a record structure characteristic: in non-null conditions, there is a unique header node and a unique End Node of the table. Except for the first element, each data element has only one precursor, except the last element, each data element has only one successor. Note: The data elements in a linear table can be various, but the elements in a linear table must have the same characteristics (belong to the same data object, similar to an array ). Ordered even relationship between data elements in a linear table. The sequential representation of a linear table and the implementation of a set of address contiguous memory units, in these continuous memory units, sequential storage of linear table data element features: logical and physical addresses are continuous and suitable for random access. Assume that & a1 is the base address of a linear table, and each data element occupies 1 storage unit. The storage address of the I element in the table: & a (I) = & a (1) + (I-1) x L linear table sequential representation structure (sequential image) it is also called an ordered table. The logical relationships of elements in an ordered table are the same as those in physical locations. It is a storage structure with random access. (Similar to arrays in advanced languages, arrays are usually used to describe the sequential storage structure of data structures ). If an array is used to represent an ordered table, it is very simple and not practical. The storage capacity cannot be changed. The following describes the representation and operation of the dynamically allocated ordered table. h header file ADTList. c file copy code 1 /********************************** * ***********************************/2 /* function definition in this file */3 /****************************** **************************************** **/4 # include "ADT. h "5 /************************************ * **********************************/6 /* category 1: for initialization, remember that all data structures must be initialized */7 /******************* **************************************** * ***********/8 9 // note that the subscript of the c array starts from 0, but the user does not know, generally choose from 1 to length location, from the user's point of view problem 10 11 // 1, linear table initialization, to construct an empty linear table, to change a linear table, you must use the pointer as the 12 int InitList (SqList * L) 13 {14 // allocate memory for the linear table in the heap, initialize elem as the first address (base address) of the memory space 15 L-> elem = (int *) malloc (LIST_INIT_SIZE * sizeof (int )); // only the table address value is stored in the structure, and the table itself is stored elsewhere. 16 // determine whether the allocation is successful. 17 if (! L-> elem) // If! L-> elem is true (null). Execute the following code 18 {19 printf ("linear table memory allocation failed! Exit the program. \ N "); 20 exit (1); // The function exits unexpectedly and returns 121} 22 to the operating system. // The table memory is allocated successfully. 23 L-> length = 0; // The table starts with an empty table and does not store any elements. Therefore, the table length is set to 024 // The storage capacity currently allocated to the linear table is 25 L-> listsize = LIST_INIT_SIZE; // initialize the storage capacity of the table, which is the maximum storage capacity of the current table 26 return 0; // return 027 if the allocation is successful} copy the code although a piece of memory space is opened in the heap to the linear table, however, you need to set a variable listsize to explicitly indicate the maximum storage capacity of the table, which is convenient for use by the program (the size of the allocated space memory and the table length are two different, the table length is the current number of elements in the table, that is, the current storage capacity of the linear table) copy code 1 /************************************ * **********************************/2 /* category 2: destroy operations, remember various Steps */3 /************************** **************************************** */4 5/2. Release the memory, destroy the table and release the memory directly! Delete operators like free () and c ++ 6 // note: the space allocated with the malloc function is released consecutively, releasing all spaces adjacent to the physical address. 7 // Therefore, only the base address can be released for the destruction of the sequence table, and all spaces will be automatically released, the linked list needs to delete nodes one by one. 8 void Destory (SqList * L) 9 {10 if (L-> elem) // if the current table still has 11 {12 free (L-> elem); // The destruction of the 13 // memory is gone, and the entire table does not exist. 14 printf ("this linear table has been destroyed! \ N "); 15} 16} copy the code. Note: The space allocated by the malloc function is released consecutively, that is, all the adjacent spaces of the physical address are released, therefore, when an ordered table is destroyed, only the base address can be released to automatically release all spaces, the linked list should delete the node one by one and copy the Code 1 /**************************** **************************************** */2/* Category 3: the reference operation does not change the data elements in the linear table, does not change the relationship between them. 3 /******************************* **************************************** */4 5 // 3. void ListEmpty (SqList L) 7 {8 // determine whether the table exists 9 if (L. elem) 10 {11 // determine whether the content is stored. 12 if (0 = L. Length) 13 {14 puts ("This table is empty! "); // Automatically wrap 15} 16 else17 {18 puts (" the table is not empty! "); 19} 20} 21 else22 {23 puts (" the table does not exist! "); 24} 25} copy the code 0 = L. length. I personally like this method to avoid errors. If you neglect it for a moment, write =, and the compilation reports an error! A constant cannot appear as the left value to remind you to copy code 1 // 4 and evaluate the length operation. If a linear table already exists, the number of elements in table L 2 int ListLength (SqList L) is returned) 3 {4 if (L. elem) 5 {6 return L. length; 7} 8 puts ("the table does not exist. The length cannot be obtained! "); 9 return 0; 10} copy the code copy code 1 // 5. Locate the operation: The linear table L already exists and returns the location of the 1st elements in the same relationship as e. 2 int LocateElem (SqList L, int e) 3 {4 int I; // locate 5 for (I = 0; I <L. length; I ++) 6 {7 // The array name itself is the first address of the array 8 if (e = L. elem [I] & I <L. length) 9 {10 printf ("positioning successful, location of this element = % d \ n", I + 1); 11 return I + 1; 12} 13} 14 puts ("locating failed! This element "); 15 return 0; 16} was not found. I personally think that the initialization operation and empty determination operation have been performed, then the other functions no longer need to write the array subscript of statement c to determine whether the table exists from 0, but it is still used to 1 corresponding to the first data element, and so on ...... 1. the time complexity analysis of the locating algorithm assumes that the table length is n optimal. If the first element satisfies the relationship, the time complexity is 0 (1) worst, if the last element satisfies the relationship or does not meet the relationship (it is still compared), the time complexity is 0 (n) 2. The average time complexity of the algorithm: Obviously, it is proportional to the table length, if it is 0 (n), copy code 1/6 and find the element. The linear table L already exists. If cur_e is an element in L, 2 void NextElem (SqList L, int cur_e) 3 {4 int I = LocateElem (L, cur_e) is returned ); // first locate the position of the reference element 5 6 if (0! = I) 7 {8 if (I = L. length) 9 {10 puts ("this is the last element, no successor! "); 11} 12 else13 {14 printf (" % d followed by % d \ n ", L. elem [I-1], L. elem [I]); 15} 16} 17 else18 {19 puts ("this element is not found in the table! "); 20} 21} copy the code. Note: Do not confuse the problem of array view with the problem of user view or the long table range. Copy code 1 // 7 to get the specified Element value. The linear table L already exists, and e returns the value of the I element in L. 2 int GetElem (SqList L, int I, int e) 3 {4 if (I <1 | I> L. length) 5 {6 puts ("beyond the search range, enter again! "); 7 return 0; 8} 9 e = L. elem [I-1]; 10 return e; 11} the copied code is not printed here, but the value is returned. It is not good because of a problem, e In the function is a local variable and a value passing parameter type. After the function is executed, the memory of e disappears and does not work. This does not affect the real parameters. Print the value of e out of the function. Copy the Code 1 int GetElem (SqList L, int I, int * e) 2 {3 if (I <1 | I> L. length) 4 {5 puts ("beyond the search range, enter again! "); 6 return 0; 7} 8 * e = L. elem [I-1]; 9 printf ("% d \ n", * e); 10 return * e; 11} copy code improvement: or add a print statement in the function, you can also change e to a variable of the pointer type to modify the real parameter and modify it in the corresponding declaration! Copy code 1/8, evaluate element precursor, linear table L already exists, if cur_e is L data, return the precursor 2 void PriorElem (SqList L, int cur_e) 3 {4 int I = LocateElem (L, cur_e); // if positioning fails, 0 5 6 if (0! = I) 7 {8 if (1 = I) 9 {10 puts ("This is the first element, NO precursor! "); 11} 12 else13 {14 printf (" % d precursor % d \ n found ", L. elem [I-1], L. elem [I-2]); 15} 16} 17 else18 {19 puts ("This element cannot be found! "); 20} 21} copy the code Note: L. elem [I-1] and L. copy the relationship between elem [I-2] And I. Code 1 // 9. traverse the elements in the table. The linear table L already exists. Print out each element in the table 2 void ListTraverse (SqList L) 3 {4 int I; 5 6 for (I = 0; I <L. length; I ++) 7 {8 printf ("% 5d", L. elem [I]); 9} 10 11} copy code % 5d, 5 print the copy code. 1 /******************************* **************************************** */2/* Class 4: processing operation */3 /********************************** ********************************** * ***/4 5 // 10. Clear the table (no memory is released). The linear table L already exists, and the table L is reset to a null table. 6 void ClearList (SqList * L) 7 {8 if (L-> elem) 9 {10 L-> length = 0; // the sequence table is left empty, the table length is 0. 11} 12}. Copy the code and destroy the memory. Copy code 1 // 11. assign a value to the table element. The linear table L already exists. The value is 1 ≤ I ≤ LengthList (L) 2 // the I-th element in the L value is e 3 void PutElem (SqList * L, int I, int e) 4 {5 if (I <1 | I> L-> length) 6 {7 puts ("beyond the table range! "); 8} 9 L-> elem [I-1] = e; 10, it is also important to insert and delete algorithms to copy code 1 // 12, insert operations, linear table L already exists, 1 ≤ I ≤ LengthList (L) + 1. Insert a new element e before the I element of L, and the length of L increases by 1. 2 void ListInsert (SqList * L, int I, int e) 3 {4 SqList * NL; // declare an extra structure pointer pointing to the allocated table memory space 5 int * j; 6 int * k; 7 // note that the subscript of the c array starts from 0, but the user does not know, generally choose from 1 to length location, in the user's point of view problem 8 // Insert before element I, after I and I, all the elements are sequentially moved to a 9 if (I <1 | I> L-> length + 1) // The last element and the last one are valid. you can insert 10 {11 puts without moving them ("beyond the table range! "); 12} 13 // The problem should be considered completely, because there may be more than one insert operation, sooner or later, the table storage capacity exceeds 14 else if (L-> length> = L-> listsize) 15 {16 // re-allocate the memory, increase storage space 17 NL-> elem = (int *) realloc (L-> elem, (L-> listsize + LISTINCREMENT) * sizeof (int); 18 if (! NL-> elem) // allocation failed, return NULL19 {20 exit (0); // exit 21} 22 // allocation successful 23 L-> elem = NL-> elem; // obtain the expanded base address 24} 25 // indicate the actual Insertion Location of the user 26 j = & (L-> elem [I-1]); // array subscript starting from 0 27 // the actual position of the last data element is length-128 for (k = & (L-> elem [L-> length-1]); k> = j; k --) // here k -- is not a reduction of 1! Instead, it is a pointer reduction operation. Each time the int type byte size changes 29 {30 * (k + 1) = * k; // elements from j to k are sequentially moved to a 31} 32 * j = e; // insert 33 L-> length ++; // do not forget the table length plus 134}. Copy Code 1. Note the operator priority. The Arrow (indirect operator) has a high priority, higher than the address & 2. parse the realloc function, which can expand or contract the space indicated by the given pointer. The content in the original memory will remain unchanged. For downgrading, the contents of the reduced part will be lost. realloc does not guarantee that the adjusted memory space is the same as the original memory space. The pointer returned by realloc is likely to point to a new address. Because realloc allocates memory from the stack, when the memory space is expanded, realloc directly obtains additional bytes from the byte after the existing data on the stack, but if the post-Data byte is not enough, the first free block with sufficient size on the stack is used. The existing data is copied to the new location, and the old block is put back on the stack. In the code, if we use the I = (int *) realloc (I, 2 * sizeof (int) re-allocate memory mode, there are two situations: Allocation successful: after the realloc function is completed, the old memory that I once directed to is automatically free. Failed to allocate, return NULL value: At this time, the memory that I originally pointed to has not been free, but the address is not found now, so memory leak appears! Solution: Define another pointer j to receive the realloc return value and determine whether the result is successful. Then, assign j to I 3. Analyze the time complexity of the Insertion Algorithm: The problem scale is the table length, the value is n. The algorithm's time is mainly spent on the for loop statement that moves elements backward. The number of cycles of this statement is (n-I + 1). The number of times the node to be moved depends not only on the table length n, but also on the insert position I. When the insert position is at the end of the table (I = n + 1), no elements need to be moved. This is the best case, and its time complexity is O (1 ). When the insert position is in the header (I = 1), all elements must be moved backward and the loop statement is executed n times. This is the worst case, and its time complexity is O (n ). 4. Average time complexity of the Insertion Algorithm: the probability of inserting an element before the pi is set to the I-th element, the expected value of the number of elements to be moved when an element is inserted in a linear table with a length of n is assumed at n + 1, with the same insertion probability, then pi = 1/(n + 1); E = pi [(n) + (n-1) + ...... + 3 + 2 + 1] = pi x (n + 1)/2) = n/2. Therefore, the insert operation on the sequence table requires an average of half of the elements to be moved. When the table length n is large, the algorithm efficiency is quite low. The average time complexity of the Insertion Algorithm is O (n ). Copy code 1 // 13. Delete the operation. The table L already exists and is not empty. The value 1 ≤ I ≤ LengthList (L ). Delete the I-th element of L and use e to return its value. The length is reduced by 1. 2 void ListDelete (SqList * L, int I, int * e) 3 {4 int * p; 5 6 if (I <1 | I> L-> length) 7 {8 puts ("I value is invalid! Enter again! "); 9} 10 else11 {12 // locate the actual location of the deleted element 13 p = & (L-> elem [I-1]); 14 * e = L-> elem [I-1]; 15 // The elements after p (excluding p) are sequentially moved forward to a 16 (; p <& (L-> elem [L-> length-1]); p ++) 17 {18 * p = * (p + 1 ); 19} 20 L-> length --; 21} 22} copy Code 1. Here e uses pointer variables so that the real parameters can be modified! 2. the time complexity analysis algorithm for deleting an algorithm is mainly used to move the element forward to the for loop statement. The number of cycles for this statement is (n-I ). It can be seen that the number of nodes to be moved depends not only on the table length n, but also on the deletion position I. When the deletion position is at the end of the table (I = n), no elements need to be moved; this is the best case, and its time complexity is O (1 ). When the delete position is in the header (I = 1), there are n-1 elements to move forward, and the loop statement is executed n-1 times, this is the worst case of its time complexity O (n ). 3. Average time complexity of the algorithm: If qi is set to the probability of deleting the I-th element, the expected value of the number of elements to be moved when an element is deleted in a linear table with a length of n is assumed, the probability of each element being deleted is the same, so qi = 1/nE = qi [(n-1) + (n-2) + ...... + 3 + 2 + 1] = 1/n x (n-1) n/2) = (n-1)/2 visible. Delete the operation on the sequence table, on average, you also need to move half of the table's elements. When the table length n is large, the algorithm efficiency is quite low. The average time complexity of the algorithm is O (n ). Copy code 1 /************************************ * **********************************/2 /* several additional complex operations */3 /******************************* **************************************** */4 5 // 1. Merge linear table AB, insert the elements in linear table B but not in linear Table A into linear Table A. 6 // only change A, without modifying B 7 void Union (SqList * LA, SqList LB) 8 {9 int I; 10 int e; 11 int lengthA = LA-> length; 12 int lengthB = LB. length; 13 14 // obtain each data element in sequence in B, and compare them in sequence in A. If not, insert 15 for (I = 1; I <= LengthB; I ++) 16 {17 GetElem (LB, I, & e); 18 if (! LocateElem (* LA, e) // A does not contain this element 19 {20 // insert it to the end of A. 21/* lengthA ++; 22 ListInsert (LA, lengthA, e); */23 ListInsert (LA, ++ lengthA, e); 24} 25} 26 Destory (& LB); 27} analyze the complexity of copying code algorithms: the execution of the GetElem function has no relationship with the table length. The insertion function is inserted at the last bit each time, and the execution time is not related to the table length. The execution time of the LocateElem function is related to the table length, the time complexity of the unordered merge algorithm mainly depends on the execution time of LocateElem. previously analyzed, the LocateElem time complexity is: 0 (lengthA), so the time complexity of this algorithm is: O (lengthA x lengthB) copy code 1 // 2. Merge the linear table AB. The elements of AB are arranged in A non-descending order by value. A and B must be merged into A new table C, and the elements of C are still in a non-decreasing order by value. Arrange 2 void MergeList (SqList LA, SqList LB, SqList * LC) 3 {4 InitList (LC); // construct the new table c 5 int lengthA = LA. length; 6 int lengthB = LB. length; 7 int lengthC = LC-> length; // The C table Initialization is empty, 0 8 int I = 1; // I mark LA 9 int j = 1; // j mark LB10 int iLA; 11 int jLB; 12 13 while (I <= lengthA) & (j <= lengthB )) 14 {15 // obtain the element values respectively, and compare 16 GetElem (LA, I, & iLA); 17 GetElem (LB, j, & jLB ); 18 if (iLA <= jLB) // LA and LB are non-descending 19 {20 lengthC ++ ;// Insert 21 ListInsert (LC, lengthC, iLA); 22 I ++; 23} 24 else25 {26 ListInsert (LC, ++ lengthC, jLB) at the end ); 27 j ++; 28} 29} 30 // AB will not be fully compared at the same time. One table must be completely inserted to c, and the other table must have 31 while (I <= lengthA) 32 {33 GetElem (LA, I ++, & iLA); 34 ListInsert (LC, ++ lengthC, iLA); // The original AB is ordered, insert all at the end of C 35} 36 // or37 while (j <= lengthB) 38 {39 GetElem (LB, j ++, & jLB); 40 ListInsert (LC, ++ lengthB, jLB); 41} 42} Time Complexity Analysis of the replication code algorithm: No matter which table AB is used, a table must be completely compared. For example A, compared with lengthA. Then, the two while statements execute one, that is, the process of inserting the remaining elements of LB into Table C in sequence, plus the number of comparisons between LB and LA, therefore, the time complexity of this algorithm is 0 (lengthA + lengthB). Instead of relying on the functions that have been defined earlier, we can use them to compare them with pointers, value assignment copy code 1 // 2. Merge linear table AB. The AB elements are arranged in A non-descending order by values. A and B must be merged into A new table C, and the elements of C are still arranged in a non-decreasing order by values. 2 void MergeList (SqList LA, SqList LB, SqList * LC) 3 {4 // or the table C is constructed first, you can only use pointers to operate 5 LC-> listsize = LA. length + LB. length; 6 LC-> length = LA. length + LB. length; 7 int * c = (int *) malloc (LC-> listsize) * Sizeof (int); 8 int * a = LA. elem; 9 int * B = LB. elem; 10 int * lastA = LA. elem + (LA. length-1) * sizeof (int); 11 int * lastB = LB. elem + (LB. length-1) * sizeof (int); 12 LC-> elem = c; 13 if (! LC-> elem) 14 {15 puts ("Table c build failed! "); 16 exit (-1); 17} 18 while (a <= lastA & B <= lastB) 19 {20 if (* a <= * B) 21 {22 * c ++ = * a ++; // from right to left, calculate * c = * a, then a ++, c ++ 23} 24 else25 {26 * c ++ = * B ++; 27} 28} 29 while (a <= lastA) 30 {31 * c ++ = * a ++; 32} 33 while (B <= lastB) 34 {35 * c ++ = * B ++; 36} 37} copy Code 1. time complexity is still 0 (lengthA + lengthB) 2. It is found that when the elements of a linear table are unordered, the time complexity of insert operations is much greater than that of sequential operations. Because an ordered linear table AB, such as increasing sequentially (not equal to each other), compares the AB element for an hour without comparing every element of B with! This ensures that the preceding element is definitely smaller than the following one. This greatly saves the running time! 3. It is also found that if two tables are merged into the new table, the new table is empty at the beginning, and you only need to insert them in sequence (in other words, assign values in turn) without moving elements, if A is merged into B (and vice versa), then the elements of B need to be moved from time to delay the time. Therefore, when using a linear table to represent an array or a set, it is best to sort the table first or merge it into a new empty table. At this point, after analyzing the concepts and common algorithms of sequence tables in a linear table, initialization, destruction, clearing, empty determination, location, insertion, and deletion are often used, traverse, forward, next, assign values, get elements, and evaluate the length. Next we will analyze the commonly used linked list.