Cảm ơn mọi người đã trả lời! Tôi nhận ra rằng câu hỏi này đã được hỏi gần hai năm trước và một câu trả lời từ lâu đã được chấp nhận, nhưng tôi đã có một chút bối rối bởi câu trả lời. Vì vậy, mặc dù người hỏi có lẽ không quan tâm đến câu trả lời mới, tôi muốn thêm phiên bản của tôi, trong trường hợp các độc giả khác cũng bối rối và ghi lại cách tiếp cận của riêng tôi. Một số điều này có thể phù hợp hơn với ý kiến, nhưng tôi chưa có tiếng để bình luận.
Đầu tiên, cho dù tôi thường xuyên xem nó như thế nào - trên bảng trắng hoặc trong trình gỡ lỗi - dường như tôi không thể tránh kết thúc bằng một vòng lặp trong nút đầu tiên, tức làchỉ vào chính nó, nếu tôi không sử dụng điều kiện để phân biệt giữa các trường hợp các nút nằm liền kề và không, ngay cả với các bước trừu tượng hoặc mã bê tông từ câu trả lời hiện tại được chấp nhận bởi Smashery. Tôi đã nhìn xung quanh một chút trên Internet để tìm mã theo phong cách của câu trả lời được chấp nhận hiện nay để tránh điều kiện như vậy, nhưng với tôi thì thật khó để tránh và các tìm kiếm của tôi không được giải quyết như vậy (trừ khi tôi sai về đề xuất có thể không chính xác). Nếu có một số biểu thức thông minh sẽ sinh ra địa chỉ của nút đầu tiên khi chúng liền kề và địa chỉ của người kế thừa nút đầu tiên khi chúng không, thì điều kiện đó sẽ không cần thiết, bởi vì tìm ra người thừa kế mới của nút thứ hai là những gì (dường như) cần thiết có điều kiện.
Thứ hai, tôi có cùng câu hỏi về các bài tập liên tiếp cho cùng một biến trong câu trả lời được chấp nhận với tư cách là người bình luận khác. Tôi hy vọng tôi không cực kỳ dày đặc ở đây, nhưng gán các giá trị khác nhau cho cùng một chuỗi theo thứ tự, trừ khi có lẽ trong trường hợp tác dụng phụ, với tôi dường như không bao giờ để biến với bất kỳ giá trị nào khác. những gì cấu hình của các nút tôi xem xét, và do đó làm cho các bài tập trước đó dường như dư thừa. Nếu tôi sai về điều đó và cách tiếp cận đó sẽ giải quyết vấn đề này thì tôi đã có thể loại bỏ điều kiện cuối cùng trong đoạn mã dưới đây, điều mà tôi đang cố gắng loại bỏ khi lần đầu tiên tôi nhìn vào Internet để tìm giải pháp trao đổi các nút mà không cần các nút lân cận đặc biệt. Tôi không hoàn toàn chắc chắn, nhưng có vẻ như Smashery đã cố tình để lại những bài tập lặp đi lặp lại đó ra khỏi sự nghiêm khắc hợp lý và minh họa tốt hơn quy trình - tôi có thể đã hiểu lầm. Thứ ba, trên trang này và các trang web khác, tôi thường thấy câu trả lời từ các câu trả lời khác lặp đi lặp lại rằng tốt hơn là trao đổi nội dung của các nút, chứ không phải là con trỏ. Tất nhiên, trong trường hợp các số nguyên đơn giản, như trong các ví dụ cho đến nay, điều đó dường như mang lại mã ngắn hơn, đơn giản hơn. Tuy nhiên, khi chúng ta thảo luận về các danh sách liên kết với các nút chứa các số nguyên, nó thường là một điểm tựa cho cấu trúc dữ liệu sao lưu của một thùng chứa phức tạp và chung chung hơn. Như vậy, tôi không nghĩ rằng việc trao đổi nội dung của các nút thực sự dễ dàng, ít nhất là nếu việc thực hiện cấu trúc dữ liệu không thể đưa ra các giả định về ngữ nghĩa sao chép của các mục của thùng chứa. Ngoài ra, có thể trao đổi xung quanh nội dung của các nút như vậy ngụ ý rằng danh sách được liên kết có quyền sở hữu nội dung của các nút đó, bởi vì nếu không mã bên ngoài phương thức của danh sách được liên kết có thể giữ tham chiếu đến đối tượng trong các nút đó. đột nhiên thay đổi bên dưới chúng.
Tôi thừa nhận, tuy nhiên, điều này có thể phụ thuộc vào ngữ nghĩa của vùng chứa. Đối với một mảng, một phương thức hoán đổi có thể được dự kiến sẽ thay đổi giá trị bên dưới các tham chiếu đến một chỉ mục nhất định của mảng đó. Điều đó có nghĩa là các tham chiếu không có nghĩa là tham chiếu đến một đối tượng cụ thể, nhưng đến một vị trí trong một vùng chứa mà một người có thể lập chỉ mục. Nếu chúng ta xem xét một danh sách liên kết như một phương tiện để chỉ đặt một tập hợp các đối tượng, có sử dụng chúng bên ngoài danh sách liên kết, người dùng có thể mong đợi một hoạt động hoán đổi để chỉ trao đổi vị trí chứ không phải nội dung.
Hãy tưởng tượng, ví dụ: danh sách được liên kết thể hiện các đối tượng thuộc loại "ô tô". Mỗi chiếc xe có một chủ sở hữu và chủ sở hữu đó tham khảo xe của họ thông qua một con trỏ đến nó. Bây giờ giả sử danh sách liên kết đại diện cho thứ tự một tập hợp các chiếc xe được dự kiến sẽ được phục vụ để kiểm tra tại một đại lý xe hơi. Nếu chúng ta hoán đổi nội dung của hai nút, để trao đổi lịch trình cho hai chiếc xe, và thực hiện nó bằng cách trao đổi nội dung của chúng, thì sự phục vụ trong thực tế sẽ xảy ra theo thứ tự mới, đúng - nhưng mọi người cũng sẽ bất ngờ sở hữu những chiếc xe khác nhau! (Tuy nhiên, tôi sẽ không đổi ý với một chiếc Tesla, vì tôi chỉ lái chiếc Corolla.)
Nếu danh sách được liên kết, như trong ví dụ của mảng, dựa trên ngữ nghĩa lập chỉ mục thì vị trí trong nút đơn giản có thể đại diện cho thứ tự mà trong đó những chiếc xe được tải lên một con tàu để vận chuyển. Tại thời điểm này, những chiếc xe không có bất kỳ chủ sở hữu nào và chúng tôi thực sự chỉ quan tâm đến vị trí của chúng. Sau đó, tôi cho rằng nó thực sự không gây tổn hại cho việc trao đổi xe, tức là nội dung của các đối tượng được các nút tham chiếu.
Cuối cùng là mã. Như tôi đã nói ở trên, tôi đã không thể tránh được vỏ bọc đặc biệt cho các nút liền kề.
Đầu tiên, định nghĩa về một phương pháp phụ trợ:
int find_node(int v, node* root, node** n, node** pn) {
int i = 0;
for (*n = root; *n != NULL && (*n)->v != v; ++i) {
*pn = *n;
*n = (*n)->next;
}
return i;
}
Phương pháp này tìm thấy một nút bởi giá trị của nó. Số nguyên được trả về là vị trí dựa trên số không (gọi nó là chỉ mục nếu bạn thích) của nút trong danh sách được liên kết. Tôi phát hiện thấy sự kề nhau thông qua vị trí, thay vì so sánh con trỏ, dễ đọc hơn. Bắt đầu danh sách là root. Phương thức thiết lập n để trỏ tới nút chứa giá trị đã truyền. Trong pn, phương thức lưu trữ tiền thân của n.
Sau đây là trao đổi thực tế:
void swap_nodes(node **root, int v1, int v2) {
if (v1 == v2) return;
node *n1, *n2, *pn1, *pn2;
int p1 = find_node(v1, *root, &n1, &pn1);
int p2 = find_node(v2, *root, &n2, &pn2);
if (p1 > p2) {
std::swap(n1, n2);
std::swap(pn1, pn2);
std::swap(p1, p2);
}
if (p1 == 0) *root = n2;
else pn1->next = n2;
node* nn1 = n1->next;
n1->next = n2->next;
if (p2 - p1 > 1) {
n2->next = nn1;
pn2->next = n1;
} else {
n2->next = n1;
}
}
Tôi xin lỗi mà tôi đã thay đổi chữ ký của phương pháp của OP một chút. Tôi thấy thuận tiện hơn khi truyền các giá trị của các nút để hoán đổi, trái ngược với các nút con trỏ. Nếu bạn chỉ chuyển các nút con trỏ đến các nút tương ứng, bạn sẽ phải thực hiện một traversal khác để tìm những người tiền nhiệm với giải pháp này, điều này cảm thấy hơi khó xử với tôi. Nếu chúng tôi không thể phân biệt các nút theo các giá trị này, ví dụ: các giá trị không phải là duy nhất, tuy nhiên, chúng tôi sẽ cần các con trỏ tới các nút.
Như với lời giải thích cho find_node ở trên, đầu tiên chúng tôi tìm vị trí, nút và tiền thân cho các giá trị nút được chuyển đến swap_nodes thông qua v1 và v2. Các giá trị cho các nút đầu tiên và thứ hai đều được hoán đổi nếu nút thứ hai hoán đổi xuất hiện trước nút đầu tiên. Nó không có nhiều mã để làm như vậy, làm giảm vỏ đặc biệt, và làm cho nó dễ dàng hơn một chút để hình dung.
Bây giờ, chúng tôi chỉ còn lại hai điều kiện nữa, không phải điều kiện nào cũng có tầm thường để tránh. Nếu nút đầu tiên ở đầu danh sách được liên kết, tức là ở vị trí 0, gốc cần phải trỏ đến nút thứ hai. Nếu không, người tiền nhiệm của nút đầu tiên sẽ trỏ đến nút thứ hai.
Giá trị trước đó của người kế thừa nút đầu tiên cần được ghi nhớ, trong trường hợp các nút không liền kề. Sau đó, người kế nhiệm của nút đầu tiên được đặt thành người kế thừa hiện tại của nút thứ hai. Đây là thay đổi duy nhất áp dụng cho tất cả các trường hợp: người kế thừa mới của nút đầu tiên là nút kế thừa cũ của nút thứ hai là sự chắc chắn duy nhất và hữu ích để bắt đầu với để nhớ các hoạt động của con trỏ và trình tự của chúng khi triển khai trao đổi này .
Cuối cùng, nếu vị trí của các nút khác nhau nhiều hơn một, chúng không liền kề. Sau đó, người kế nhiệm mới của nút thứ hai trở thành người kế thừa cũ của nút đầu tiên - được lưu ở trên - và nút tiền nhiệm của nút thứ hai bây giờ trỏ đến nút đầu tiên. Nếu chúng liền kề, không có nút giữa các nút cần trao đổi cần cập nhật, vì vậy chỉ cần liên kết nút thứ hai với nút đầu tiên là tất cả những gì còn lại để thực hiện.
Xin chào Reti, tôi đã trả lời chỉnh sửa1 trong phản hồi của tôi. Nếu bạn đang nhận được một segfault, hãy kiểm tra xem bạn đang phân bổ chính xác và xóa biến tmp. – Smashery